From 654ddcd40984dbca386e02fc28056f430fc18ae3 Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Wed, 11 Dec 2024 17:43:35 +0100 Subject: [PATCH] frontend: Split Qt UI Widget implementations into single files per class --- frontend/components/VolumeSlider.cpp | 1431 +-- frontend/components/VolumeSlider.hpp | 320 +- frontend/utility/AdvancedOutput.cpp | 1637 +-- frontend/utility/AdvancedOutput.hpp | 2498 +--- frontend/utility/BasicOutputHandler.cpp | 2011 +-- frontend/utility/BasicOutputHandler.hpp | 51 +- frontend/utility/QuickTransition.cpp | 1662 +-- frontend/utility/QuickTransition.hpp | 1338 +- frontend/utility/SceneRenameDelegate.cpp | 10156 +-------------- frontend/utility/SceneRenameDelegate.hpp | 1351 -- frontend/utility/ScreenshotObj.cpp | 43 +- frontend/utility/SimpleOutput.cpp | 1651 +-- frontend/utility/SimpleOutput.hpp | 2486 +--- .../StartMultiTrackVideoStreamingGuard.hpp | 2525 +--- frontend/utility/SurfaceEventFilter.hpp | 213 +- frontend/utility/VolumeMeterTimer.cpp | 1485 +-- frontend/utility/VolumeMeterTimer.hpp | 324 +- frontend/widgets/ColorSelect.cpp | 10180 +--------------- frontend/widgets/ColorSelect.hpp | 1353 +- frontend/widgets/OBSBasic.cpp | 8337 +------------ frontend/widgets/OBSBasic.hpp | 2234 ++-- frontend/widgets/OBSBasicStatusBar.cpp | 21 +- frontend/widgets/OBSBasicStatusBar.hpp | 24 +- frontend/widgets/OBSBasic_Browser.cpp | 25 +- frontend/widgets/OBSBasic_Clipboard.cpp | 9964 +-------------- frontend/widgets/OBSBasic_ContextToolbar.cpp | 9936 +-------------- frontend/widgets/OBSBasic_Docks.cpp | 9856 +-------------- frontend/widgets/OBSBasic_Dropfiles.cpp | 33 +- frontend/widgets/OBSBasic_Hotkeys.cpp | 9897 +-------------- frontend/widgets/OBSBasic_Icons.cpp | 21 +- frontend/widgets/OBSBasic_MainControls.cpp | 9589 +-------------- frontend/widgets/OBSBasic_OutputHandler.cpp | 10070 +-------------- frontend/widgets/OBSBasic_Preview.cpp | 9556 +-------------- frontend/widgets/OBSBasic_Profiles.cpp | 26 +- frontend/widgets/OBSBasic_Projectors.cpp | 9933 +-------------- frontend/widgets/OBSBasic_Recording.cpp | 9809 +-------------- frontend/widgets/OBSBasic_ReplayBuffer.cpp | 9999 +-------------- frontend/widgets/OBSBasic_SceneItems.cpp | 8818 +------------ frontend/widgets/OBSBasic_Scenes.cpp | 9248 +------------- frontend/widgets/OBSBasic_Screenshots.cpp | 299 +- frontend/widgets/OBSBasic_Service.cpp | 10083 +-------------- frontend/widgets/OBSBasic_StatusBar.cpp | 10179 +-------------- frontend/widgets/OBSBasic_Streaming.cpp | 9775 +-------------- frontend/widgets/OBSBasic_SysTray.cpp | 10060 +-------------- frontend/widgets/OBSBasic_Updater.cpp | 9997 +-------------- frontend/widgets/OBSBasic_VirtualCam.cpp | 10037 +-------------- frontend/widgets/OBSBasic_VolControl.cpp | 9890 +-------------- frontend/widgets/OBSBasic_YouTube.cpp | 9961 +-------------- frontend/widgets/OBSQTDisplay.cpp | 61 +- frontend/widgets/OBSQTDisplay.hpp | 3 +- frontend/widgets/StatusBarWidget.cpp | 595 +- frontend/widgets/StatusBarWidget.hpp | 104 +- frontend/widgets/VolControl.cpp | 1123 +- frontend/widgets/VolControl.hpp | 285 +- .../widgets/VolumeAccessibleInterface.cpp | 1450 +-- .../widgets/VolumeAccessibleInterface.hpp | 315 - frontend/widgets/VolumeMeter.cpp | 567 +- frontend/widgets/VolumeMeter.hpp | 129 +- 58 files changed, 1723 insertions(+), 253301 deletions(-) diff --git a/frontend/components/VolumeSlider.cpp b/frontend/components/VolumeSlider.cpp index cd00c14d7..4ef9b5d22 100644 --- a/frontend/components/VolumeSlider.cpp +++ b/frontend/components/VolumeSlider.cpp @@ -1,1377 +1,8 @@ -#include "window-basic-main.hpp" -#include "moc_volume-control.cpp" -#include "obs-app.hpp" -#include "mute-checkbox.hpp" -#include "absolute-slider.hpp" -#include "source-label.hpp" +#include "VolumeSlider.hpp" -#include -#include -#include -#include -#include -#include #include -using namespace std; - -#define FADER_PRECISION 4096.0 - -// Size of the audio indicator in pixels -#define INDICATOR_THICKNESS 3 - -// Padding on top and bottom of vertical meters -#define METER_PADDING 1 - -std::weak_ptr VolumeMeter::updateTimer; - -static inline Qt::CheckState GetCheckState(bool muted, bool unassigned) -{ - if (muted) - return Qt::Checked; - else if (unassigned) - return Qt::PartiallyChecked; - else - return Qt::Unchecked; -} - -static inline bool IsSourceUnassigned(obs_source_t *source) -{ - uint32_t mixes = (obs_source_get_audio_mixers(source) & ((1 << MAX_AUDIO_MIXES) - 1)); - obs_monitoring_type mt = obs_source_get_monitoring_type(source); - - return mixes == 0 && mt != OBS_MONITORING_TYPE_MONITOR_ONLY; -} - -static void ShowUnassignedWarning(const char *name) -{ - auto msgBox = [=]() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("VolControl.UnassignedWarning.Title")); - msgbox.setText(QTStr("VolControl.UnassignedWarning.Text").arg(name)); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); -} - -void VolControl::OBSVolumeChanged(void *data, float db) -{ - Q_UNUSED(db); - VolControl *volControl = static_cast(data); - - QMetaObject::invokeMethod(volControl, "VolumeChanged"); -} - -void VolControl::OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], - const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]) -{ - VolControl *volControl = static_cast(data); - - volControl->volMeter->setLevels(magnitude, peak, inputPeak); -} - -void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata) -{ - VolControl *volControl = static_cast(data); - bool muted = calldata_bool(calldata, "muted"); - - QMetaObject::invokeMethod(volControl, "VolumeMuted", Q_ARG(bool, muted)); -} - -void VolControl::VolumeChanged() -{ - slider->blockSignals(true); - slider->setValue((int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION)); - slider->blockSignals(false); - - updateText(); -} - -void VolControl::VolumeMuted(bool muted) -{ - bool unassigned = IsSourceUnassigned(source); - - auto newState = GetCheckState(muted, unassigned); - if (mute->checkState() != newState) - mute->setCheckState(newState); - - volMeter->muted = muted || unassigned; -} - -void VolControl::OBSMixersOrMonitoringChanged(void *data, calldata_t *) -{ - - VolControl *volControl = static_cast(data); - QMetaObject::invokeMethod(volControl, "MixersOrMonitoringChanged", Qt::QueuedConnection); -} - -void VolControl::MixersOrMonitoringChanged() -{ - bool muted = obs_source_muted(source); - bool unassigned = IsSourceUnassigned(source); - - auto newState = GetCheckState(muted, unassigned); - if (mute->checkState() != newState) - mute->setCheckState(newState); - - volMeter->muted = muted || unassigned; -} - -void VolControl::SetMuted(bool) -{ - bool checked = mute->checkState() == Qt::Checked; - bool prev = obs_source_muted(source); - obs_source_set_muted(source, checked); - bool unassigned = IsSourceUnassigned(source); - - if (!checked && unassigned) { - mute->setCheckState(Qt::PartiallyChecked); - /* Show notice about the source no being assigned to any tracks */ - bool has_shown_warning = - config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources"); - if (!has_shown_warning) - ShowUnassignedWarning(obs_source_get_name(source)); - } - - auto undo_redo = [](const std::string &uuid, bool val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_muted(source, val); - }; - - QString text = QTStr(checked ? "Undo.Volume.Mute" : "Undo.Volume.Unmute"); - - const char *name = obs_source_get_name(source); - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(text.arg(name), std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, checked), uuid, uuid); -} - -void VolControl::SliderChanged(int vol) -{ - float prev = obs_source_get_volume(source); - - obs_fader_set_deflection(obs_fader, float(vol) / FADER_PRECISION); - updateText(); - - auto undo_redo = [](const std::string &uuid, float val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_volume(source, val); - }; - - float val = obs_source_get_volume(source); - const char *name = obs_source_get_name(source); - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(QTStr("Undo.Volume.Change").arg(name), - std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, val), uuid, uuid, true); -} - -void VolControl::updateText() -{ - QString text; - float db = obs_fader_get_db(obs_fader); - - if (db < -96.0f) - text = "-inf dB"; - else - text = QString::number(db, 'f', 1).append(" dB"); - - volLabel->setText(text); - - bool muted = obs_source_muted(source); - const char *accTextLookup = muted ? "VolControl.SliderMuted" : "VolControl.SliderUnmuted"; - - QString sourceName = obs_source_get_name(source); - QString accText = QTStr(accTextLookup).arg(sourceName); - - slider->setAccessibleName(accText); -} - -void VolControl::EmitConfigClicked() -{ - emit ConfigClicked(); -} - -void VolControl::SetMeterDecayRate(qreal q) -{ - volMeter->setPeakDecayRate(q); -} - -void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType) -{ - volMeter->setPeakMeterType(peakMeterType); -} - -VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) - : source(std::move(source_)), - levelTotal(0.0f), - levelCount(0.0f), - obs_fader(obs_fader_create(OBS_FADER_LOG)), - obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)), - vertical(vertical), - contextMenu(nullptr) -{ - nameLabel = new OBSSourceLabel(source); - volLabel = new QLabel(); - mute = new MuteCheckBox(); - - volLabel->setObjectName("volLabel"); - volLabel->setAlignment(Qt::AlignCenter); - -#ifdef __APPLE__ - mute->setAttribute(Qt::WA_LayoutUsesWidgetRect); -#endif - - QString sourceName = obs_source_get_name(source); - setObjectName(sourceName); - - if (showConfig) { - config = new QPushButton(this); - config->setProperty("class", "icon-dots-vert"); - config->setAutoDefault(false); - - config->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - config->setAccessibleName(QTStr("VolControl.Properties").arg(sourceName)); - - connect(config, &QAbstractButton::clicked, this, &VolControl::EmitConfigClicked); - } - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(0); - - if (vertical) { - QHBoxLayout *nameLayout = new QHBoxLayout; - QHBoxLayout *controlLayout = new QHBoxLayout; - QHBoxLayout *volLayout = new QHBoxLayout; - QFrame *meterFrame = new QFrame; - QHBoxLayout *meterLayout = new QHBoxLayout; - - volMeter = new VolumeMeter(nullptr, obs_volmeter, true); - slider = new VolumeSlider(obs_fader, Qt::Vertical); - slider->setLayoutDirection(Qt::LeftToRight); - slider->setDisplayTicks(true); - - nameLayout->setAlignment(Qt::AlignCenter); - meterLayout->setAlignment(Qt::AlignCenter); - controlLayout->setAlignment(Qt::AlignCenter); - volLayout->setAlignment(Qt::AlignCenter); - - meterFrame->setObjectName("volMeterFrame"); - - nameLayout->setContentsMargins(0, 0, 0, 0); - nameLayout->setSpacing(0); - nameLayout->addWidget(nameLabel); - - controlLayout->setContentsMargins(0, 0, 0, 0); - controlLayout->setSpacing(0); - - // Add Headphone (audio monitoring) widget here - controlLayout->addWidget(mute); - - if (showConfig) { - controlLayout->addWidget(config); - } - - meterLayout->setContentsMargins(0, 0, 0, 0); - meterLayout->setSpacing(0); - meterLayout->addWidget(slider); - meterLayout->addWidget(volMeter); - - meterFrame->setLayout(meterLayout); - - volLayout->setContentsMargins(0, 0, 0, 0); - volLayout->setSpacing(0); - volLayout->addWidget(volLabel); - volLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum)); - - mainLayout->addItem(nameLayout); - mainLayout->addItem(volLayout); - mainLayout->addWidget(meterFrame); - mainLayout->addItem(controlLayout); - - volMeter->setFocusProxy(slider); - - // Default size can cause clipping of long names in vertical layout. - QFont font = nameLabel->font(); - QFontInfo info(font); - nameLabel->setFont(font); - - setMaximumWidth(110); - } else { - QHBoxLayout *textLayout = new QHBoxLayout; - QHBoxLayout *controlLayout = new QHBoxLayout; - QFrame *meterFrame = new QFrame; - QVBoxLayout *meterLayout = new QVBoxLayout; - QVBoxLayout *buttonLayout = new QVBoxLayout; - - volMeter = new VolumeMeter(nullptr, obs_volmeter, false); - volMeter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - - slider = new VolumeSlider(obs_fader, Qt::Horizontal); - slider->setLayoutDirection(Qt::LeftToRight); - slider->setDisplayTicks(true); - - textLayout->setContentsMargins(0, 0, 0, 0); - textLayout->addWidget(nameLabel); - textLayout->addWidget(volLabel); - textLayout->setAlignment(nameLabel, Qt::AlignLeft); - textLayout->setAlignment(volLabel, Qt::AlignRight); - - meterFrame->setObjectName("volMeterFrame"); - meterFrame->setLayout(meterLayout); - - meterLayout->setContentsMargins(0, 0, 0, 0); - meterLayout->setSpacing(0); - - meterLayout->addWidget(volMeter); - meterLayout->addWidget(slider); - - buttonLayout->setContentsMargins(0, 0, 0, 0); - buttonLayout->setSpacing(0); - - if (showConfig) { - buttonLayout->addWidget(config); - } - buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)); - buttonLayout->addWidget(mute); - - controlLayout->addItem(buttonLayout); - controlLayout->addWidget(meterFrame); - - mainLayout->addItem(textLayout); - mainLayout->addItem(controlLayout); - - volMeter->setFocusProxy(slider); - } - - setLayout(mainLayout); - - nameLabel->setText(sourceName); - - slider->setMinimum(0); - slider->setMaximum(int(FADER_PRECISION)); - - bool muted = obs_source_muted(source); - bool unassigned = IsSourceUnassigned(source); - mute->setCheckState(GetCheckState(muted, unassigned)); - volMeter->muted = muted || unassigned; - mute->setAccessibleName(QTStr("VolControl.Mute").arg(sourceName)); - obs_fader_add_callback(obs_fader, OBSVolumeChanged, this); - obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this); - - sigs.emplace_back(obs_source_get_signal_handler(source), "mute", OBSVolumeMuted, this); - sigs.emplace_back(obs_source_get_signal_handler(source), "audio_mixers", OBSMixersOrMonitoringChanged, this); - sigs.emplace_back(obs_source_get_signal_handler(source), "audio_monitoring", OBSMixersOrMonitoringChanged, - this); - - QWidget::connect(slider, &VolumeSlider::valueChanged, this, &VolControl::SliderChanged); - QWidget::connect(mute, &MuteCheckBox::clicked, this, &VolControl::SetMuted); - - obs_fader_attach_source(obs_fader, source); - obs_volmeter_attach_source(obs_volmeter, source); - - /* Call volume changed once to init the slider position and label */ - VolumeChanged(); -} - -void VolControl::EnableSlider(bool enable) -{ - slider->setEnabled(enable); -} - -VolControl::~VolControl() -{ - obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this); - obs_volmeter_remove_callback(obs_volmeter, OBSVolumeLevel, this); - - sigs.clear(); - - if (contextMenu) - contextMenu->close(); -} - -static inline QColor color_from_int(long long val) -{ - QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); - color.setAlpha(255); - - return color; -} - -QColor VolumeMeter::getBackgroundNominalColor() const -{ - return p_backgroundNominalColor; -} - -QColor VolumeMeter::getBackgroundNominalColorDisabled() const -{ - return backgroundNominalColorDisabled; -} - -void VolumeMeter::setBackgroundNominalColor(QColor c) -{ - p_backgroundNominalColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundNominalColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreen")); - } else { - backgroundNominalColor = p_backgroundNominalColor; - } -} - -void VolumeMeter::setBackgroundNominalColorDisabled(QColor c) -{ - backgroundNominalColorDisabled = std::move(c); -} - -QColor VolumeMeter::getBackgroundWarningColor() const -{ - return p_backgroundWarningColor; -} - -QColor VolumeMeter::getBackgroundWarningColorDisabled() const -{ - return backgroundWarningColorDisabled; -} - -void VolumeMeter::setBackgroundWarningColor(QColor c) -{ - p_backgroundWarningColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundWarningColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellow")); - } else { - backgroundWarningColor = p_backgroundWarningColor; - } -} - -void VolumeMeter::setBackgroundWarningColorDisabled(QColor c) -{ - backgroundWarningColorDisabled = std::move(c); -} - -QColor VolumeMeter::getBackgroundErrorColor() const -{ - return p_backgroundErrorColor; -} - -QColor VolumeMeter::getBackgroundErrorColorDisabled() const -{ - return backgroundErrorColorDisabled; -} - -void VolumeMeter::setBackgroundErrorColor(QColor c) -{ - p_backgroundErrorColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundErrorColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerRed")); - } else { - backgroundErrorColor = p_backgroundErrorColor; - } -} - -void VolumeMeter::setBackgroundErrorColorDisabled(QColor c) -{ - backgroundErrorColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundNominalColor() const -{ - return p_foregroundNominalColor; -} - -QColor VolumeMeter::getForegroundNominalColorDisabled() const -{ - return foregroundNominalColorDisabled; -} - -void VolumeMeter::setForegroundNominalColor(QColor c) -{ - p_foregroundNominalColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundNominalColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreenActive")); - } else { - foregroundNominalColor = p_foregroundNominalColor; - } -} - -void VolumeMeter::setForegroundNominalColorDisabled(QColor c) -{ - foregroundNominalColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundWarningColor() const -{ - return p_foregroundWarningColor; -} - -QColor VolumeMeter::getForegroundWarningColorDisabled() const -{ - return foregroundWarningColorDisabled; -} - -void VolumeMeter::setForegroundWarningColor(QColor c) -{ - p_foregroundWarningColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundWarningColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellowActive")); - } else { - foregroundWarningColor = p_foregroundWarningColor; - } -} - -void VolumeMeter::setForegroundWarningColorDisabled(QColor c) -{ - foregroundWarningColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundErrorColor() const -{ - return p_foregroundErrorColor; -} - -QColor VolumeMeter::getForegroundErrorColorDisabled() const -{ - return foregroundErrorColorDisabled; -} - -void VolumeMeter::setForegroundErrorColor(QColor c) -{ - p_foregroundErrorColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundErrorColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerRedActive")); - } else { - foregroundErrorColor = p_foregroundErrorColor; - } -} - -void VolumeMeter::setForegroundErrorColorDisabled(QColor c) -{ - foregroundErrorColorDisabled = std::move(c); -} - -QColor VolumeMeter::getClipColor() const -{ - return clipColor; -} - -void VolumeMeter::setClipColor(QColor c) -{ - clipColor = std::move(c); -} - -QColor VolumeMeter::getMagnitudeColor() const -{ - return magnitudeColor; -} - -void VolumeMeter::setMagnitudeColor(QColor c) -{ - magnitudeColor = std::move(c); -} - -QColor VolumeMeter::getMajorTickColor() const -{ - return majorTickColor; -} - -void VolumeMeter::setMajorTickColor(QColor c) -{ - majorTickColor = std::move(c); -} - -QColor VolumeMeter::getMinorTickColor() const -{ - return minorTickColor; -} - -void VolumeMeter::setMinorTickColor(QColor c) -{ - minorTickColor = std::move(c); -} - -int VolumeMeter::getMeterThickness() const -{ - return meterThickness; -} - -void VolumeMeter::setMeterThickness(int v) -{ - meterThickness = v; - recalculateLayout = true; -} - -qreal VolumeMeter::getMeterFontScaling() const -{ - return meterFontScaling; -} - -void VolumeMeter::setMeterFontScaling(qreal v) -{ - meterFontScaling = v; - recalculateLayout = true; -} - -void VolControl::refreshColors() -{ - volMeter->setBackgroundNominalColor(volMeter->getBackgroundNominalColor()); - volMeter->setBackgroundWarningColor(volMeter->getBackgroundWarningColor()); - volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor()); - volMeter->setForegroundNominalColor(volMeter->getForegroundNominalColor()); - volMeter->setForegroundWarningColor(volMeter->getForegroundWarningColor()); - volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor()); -} - -qreal VolumeMeter::getMinimumLevel() const -{ - return minimumLevel; -} - -void VolumeMeter::setMinimumLevel(qreal v) -{ - minimumLevel = v; -} - -qreal VolumeMeter::getWarningLevel() const -{ - return warningLevel; -} - -void VolumeMeter::setWarningLevel(qreal v) -{ - warningLevel = v; -} - -qreal VolumeMeter::getErrorLevel() const -{ - return errorLevel; -} - -void VolumeMeter::setErrorLevel(qreal v) -{ - errorLevel = v; -} - -qreal VolumeMeter::getClipLevel() const -{ - return clipLevel; -} - -void VolumeMeter::setClipLevel(qreal v) -{ - clipLevel = v; -} - -qreal VolumeMeter::getMinimumInputLevel() const -{ - return minimumInputLevel; -} - -void VolumeMeter::setMinimumInputLevel(qreal v) -{ - minimumInputLevel = v; -} - -qreal VolumeMeter::getPeakDecayRate() const -{ - return peakDecayRate; -} - -void VolumeMeter::setPeakDecayRate(qreal v) -{ - peakDecayRate = v; -} - -qreal VolumeMeter::getMagnitudeIntegrationTime() const -{ - return magnitudeIntegrationTime; -} - -void VolumeMeter::setMagnitudeIntegrationTime(qreal v) -{ - magnitudeIntegrationTime = v; -} - -qreal VolumeMeter::getPeakHoldDuration() const -{ - return peakHoldDuration; -} - -void VolumeMeter::setPeakHoldDuration(qreal v) -{ - peakHoldDuration = v; -} - -qreal VolumeMeter::getInputPeakHoldDuration() const -{ - return inputPeakHoldDuration; -} - -void VolumeMeter::setInputPeakHoldDuration(qreal v) -{ - inputPeakHoldDuration = v; -} - -void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType) -{ - obs_volmeter_set_peak_meter_type(obs_volmeter, peakMeterType); - switch (peakMeterType) { - case TRUE_PEAK_METER: - // For true-peak meters EBU has defined the Permitted Maximum, - // taking into account the accuracy of the meter and further - // processing required by lossy audio compression. - // - // The alignment level was not specified, but I've adjusted - // it compared to a sample-peak meter. Incidentally Youtube - // uses this new Alignment Level as the maximum integrated - // loudness of a video. - // - // * Permitted Maximum Level (PML) = -2.0 dBTP - // * Alignment Level (AL) = -13 dBTP - setErrorLevel(-2.0); - setWarningLevel(-13.0); - break; - - case SAMPLE_PEAK_METER: - default: - // For a sample Peak Meter EBU has the following level - // definitions, taking into account inaccuracies of this meter: - // - // * Permitted Maximum Level (PML) = -9.0 dBFS - // * Alignment Level (AL) = -20.0 dBFS - setErrorLevel(-9.0); - setWarningLevel(-20.0); - break; - } -} - -void VolumeMeter::mousePressEvent(QMouseEvent *event) -{ - setFocus(Qt::MouseFocusReason); - event->accept(); -} - -void VolumeMeter::wheelEvent(QWheelEvent *event) -{ - QApplication::sendEvent(focusProxy(), event); -} - -VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter, bool vertical) - : QWidget(parent), - obs_volmeter(obs_volmeter), - vertical(vertical) -{ - setAttribute(Qt::WA_OpaquePaintEvent, true); - - // Default meter settings, they only show if - // there is no stylesheet, do not remove. - backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green - backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow - backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red - foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green - foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow - foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red - - backgroundNominalColorDisabled.setRgb(90, 90, 90); - backgroundWarningColorDisabled.setRgb(117, 117, 117); - backgroundErrorColorDisabled.setRgb(65, 65, 65); - foregroundNominalColorDisabled.setRgb(163, 163, 163); - foregroundWarningColorDisabled.setRgb(217, 217, 217); - foregroundErrorColorDisabled.setRgb(113, 113, 113); - - clipColor.setRgb(0xff, 0xff, 0xff); // Bright white - magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black - majorTickColor.setRgb(0x00, 0x00, 0x00); // Black - minorTickColor.setRgb(0x32, 0x32, 0x32); // Dark gray - minimumLevel = -60.0; // -60 dB - warningLevel = -20.0; // -20 dB - errorLevel = -9.0; // -9 dB - clipLevel = -0.5; // -0.5 dB - minimumInputLevel = -50.0; // -50 dB - peakDecayRate = 11.76; // 20 dB / 1.7 sec - magnitudeIntegrationTime = 0.3; // 99% in 300 ms - peakHoldDuration = 20.0; // 20 seconds - inputPeakHoldDuration = 1.0; // 1 second - meterThickness = 3; // Bar thickness in pixels - meterFontScaling = 0.7; // Font size for numbers is 70% of Widget's font size - channels = (int)audio_output_get_channels(obs_get_audio()); - - doLayout(); - updateTimerRef = updateTimer.lock(); - if (!updateTimerRef) { - updateTimerRef = std::make_shared(); - updateTimerRef->setTimerType(Qt::PreciseTimer); - updateTimerRef->start(16); - updateTimer = updateTimerRef; - } - - updateTimerRef->AddVolControl(this); -} - -VolumeMeter::~VolumeMeter() -{ - updateTimerRef->RemoveVolControl(this); -} - -void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], - const float inputPeak[MAX_AUDIO_CHANNELS]) -{ - uint64_t ts = os_gettime_ns(); - QMutexLocker locker(&dataMutex); - - currentLastUpdateTime = ts; - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { - currentMagnitude[channelNr] = magnitude[channelNr]; - currentPeak[channelNr] = peak[channelNr]; - currentInputPeak[channelNr] = inputPeak[channelNr]; - } - - // In case there are more updates then redraws we must make sure - // that the ballistics of peak and hold are recalculated. - locker.unlock(); - calculateBallistics(ts); -} - -inline void VolumeMeter::resetLevels() -{ - currentLastUpdateTime = 0; - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { - currentMagnitude[channelNr] = -M_INFINITE; - currentPeak[channelNr] = -M_INFINITE; - currentInputPeak[channelNr] = -M_INFINITE; - - displayMagnitude[channelNr] = -M_INFINITE; - displayPeak[channelNr] = -M_INFINITE; - displayPeakHold[channelNr] = -M_INFINITE; - displayPeakHoldLastUpdateTime[channelNr] = 0; - displayInputPeakHold[channelNr] = -M_INFINITE; - displayInputPeakHoldLastUpdateTime[channelNr] = 0; - } -} - -bool VolumeMeter::needLayoutChange() -{ - int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); - - if (!currentNrAudioChannels) { - struct obs_audio_info oai; - obs_get_audio_info(&oai); - currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1 : 2; - } - - if (displayNrAudioChannels != currentNrAudioChannels) { - displayNrAudioChannels = currentNrAudioChannels; - recalculateLayout = true; - } - - return recalculateLayout; -} - -// When this is called from the constructor, obs_volmeter_get_nr_channels has not -// yet been called and Q_PROPERTY settings have not yet been read from the -// stylesheet. -inline void VolumeMeter::doLayout() -{ - QMutexLocker locker(&dataMutex); - - if (displayNrAudioChannels) { - int meterSize = std::floor(22 / displayNrAudioChannels); - setMeterThickness(std::clamp(meterSize, 3, 7)); - } - recalculateLayout = false; - - tickFont = font(); - QFontInfo info(tickFont); - tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling); - QFontMetrics metrics(tickFont); - if (vertical) { - // Each meter channel is meterThickness pixels wide, plus one pixel - // between channels, but not after the last. - // Add 4 pixels for ticks, space to hold our longest label in this font, - // and a few pixels before the fader. - QRect scaleBounds = metrics.boundingRect("-88"); - setMinimumSize(displayNrAudioChannels * (meterThickness + 1) - 1 + 10 + scaleBounds.width() + 2, 100); - } else { - // Each meter channel is meterThickness pixels high, plus one pixel - // between channels, but not after the last. - // Add 4 pixels for ticks, and space high enough to hold our label in - // this font, presuming that digits don't have descenders. - setMinimumSize(100, displayNrAudioChannels * (meterThickness + 1) - 1 + 4 + metrics.capHeight()); - } - - resetLevels(); -} - -inline bool VolumeMeter::detectIdle(uint64_t ts) -{ - double timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001; - if (timeSinceLastUpdate > 0.5) { - resetLevels(); - return true; - } else { - return false; - } -} - -inline void VolumeMeter::calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw) -{ - if (currentPeak[channelNr] >= displayPeak[channelNr] || isnan(displayPeak[channelNr])) { - // Attack of peak is immediate. - displayPeak[channelNr] = currentPeak[channelNr]; - } else { - // Decay of peak is 40 dB / 1.7 seconds for Fast Profile - // 20 dB / 1.7 seconds for Medium Profile (Type I PPM) - // 24 dB / 2.8 seconds for Slow Profile (Type II PPM) - float decay = float(peakDecayRate * timeSinceLastRedraw); - displayPeak[channelNr] = - std::clamp(displayPeak[channelNr] - decay, std::min(currentPeak[channelNr], 0.f), 0.f); - } - - if (currentPeak[channelNr] >= displayPeakHold[channelNr] || !isfinite(displayPeakHold[channelNr])) { - // Attack of peak-hold is immediate, but keep track - // when it was last updated. - displayPeakHold[channelNr] = currentPeak[channelNr]; - displayPeakHoldLastUpdateTime[channelNr] = ts; - } else { - // The peak and hold falls back to peak - // after 20 seconds. - qreal timeSinceLastPeak = (uint64_t)(ts - displayPeakHoldLastUpdateTime[channelNr]) * 0.000000001; - if (timeSinceLastPeak > peakHoldDuration) { - displayPeakHold[channelNr] = currentPeak[channelNr]; - displayPeakHoldLastUpdateTime[channelNr] = ts; - } - } - - if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] || - !isfinite(displayInputPeakHold[channelNr])) { - // Attack of peak-hold is immediate, but keep track - // when it was last updated. - displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; - displayInputPeakHoldLastUpdateTime[channelNr] = ts; - } else { - // The peak and hold falls back to peak after 1 second. - qreal timeSinceLastPeak = (uint64_t)(ts - displayInputPeakHoldLastUpdateTime[channelNr]) * 0.000000001; - if (timeSinceLastPeak > inputPeakHoldDuration) { - displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; - displayInputPeakHoldLastUpdateTime[channelNr] = ts; - } - } - - if (!isfinite(displayMagnitude[channelNr])) { - // The statements in the else-leg do not work with - // NaN and infinite displayMagnitude. - displayMagnitude[channelNr] = currentMagnitude[channelNr]; - } else { - // A VU meter will integrate to the new value to 99% in 300 ms. - // The calculation here is very simplified and is more accurate - // with higher frame-rate. - float attack = float((currentMagnitude[channelNr] - displayMagnitude[channelNr]) * - (timeSinceLastRedraw / magnitudeIntegrationTime) * 0.99); - displayMagnitude[channelNr] = - std::clamp(displayMagnitude[channelNr] + attack, (float)minimumLevel, 0.f); - } -} - -inline void VolumeMeter::calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw) -{ - QMutexLocker locker(&dataMutex); - - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) - calculateBallisticsForChannel(channelNr, ts, timeSinceLastRedraw); -} - -void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width, int height, float peakHold) -{ - QMutexLocker locker(&dataMutex); - QColor color; - - if (peakHold < minimumInputLevel) - color = backgroundNominalColor; - else if (peakHold < warningLevel) - color = foregroundNominalColor; - else if (peakHold < errorLevel) - color = foregroundWarningColor; - else if (peakHold <= clipLevel) - color = foregroundErrorColor; - else - color = clipColor; - - painter.fillRect(x, y, width, height, color); -} - -void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width) -{ - qreal scale = width / minimumLevel; - - painter.setFont(tickFont); - QFontMetrics metrics(tickFont); - painter.setPen(majorTickColor); - - // Draw major tick lines and numeric indicators. - for (int i = 0; i >= minimumLevel; i -= 5) { - int position = int(x + width - (i * scale) - 1); - QString str = QString::number(i); - - // Center the number on the tick, but don't overflow - QRect textBounds = metrics.boundingRect(str); - int pos; - if (i == 0) { - pos = position - textBounds.width(); - } else { - pos = position - (textBounds.width() / 2); - if (pos < 0) - pos = 0; - } - painter.drawText(pos, y + 4 + metrics.capHeight(), str); - - painter.drawLine(position, y, position, y + 2); - } -} - -void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height) -{ - qreal scale = height / minimumLevel; - - painter.setFont(tickFont); - QFontMetrics metrics(tickFont); - painter.setPen(majorTickColor); - - // Draw major tick lines and numeric indicators. - for (int i = 0; i >= minimumLevel; i -= 5) { - int position = y + int(i * scale) + METER_PADDING; - QString str = QString::number(i); - - // Center the number on the tick, but don't overflow - if (i == 0) { - painter.drawText(x + 10, position + metrics.capHeight(), str); - } else { - painter.drawText(x + 8, position + (metrics.capHeight() / 2), str); - } - - painter.drawLine(x, position, x + 2, position); - } -} - -#define CLIP_FLASH_DURATION_MS 1000 - -inline int VolumeMeter::convertToInt(float number) -{ - constexpr int min = std::numeric_limits::min(); - constexpr int max = std::numeric_limits::max(); - - // NOTE: Conversion from 'const int' to 'float' changes max value from 2147483647 to 2147483648 - if (number >= (float)max) - return max; - else if (number < min) - return min; - else - return int(number); -} - -void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold) -{ - qreal scale = width / minimumLevel; - - QMutexLocker locker(&dataMutex); - int minimumPosition = x + 0; - int maximumPosition = x + width; - int magnitudePosition = x + width - convertToInt(magnitude * scale); - int peakPosition = x + width - convertToInt(peak * scale); - int peakHoldPosition = x + width - convertToInt(peakHold * scale); - int warningPosition = x + width - convertToInt(warningLevel * scale); - int errorPosition = x + width - convertToInt(errorLevel * scale); - - int nominalLength = warningPosition - minimumPosition; - int warningLength = errorPosition - warningPosition; - int errorLength = maximumPosition - errorPosition; - locker.unlock(); - - if (clipping) { - peakPosition = maximumPosition; - } - - if (peakPosition < minimumPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < warningPosition) { - painter.fillRect(minimumPosition, y, peakPosition - minimumPosition, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(peakPosition, y, warningPosition - peakPosition, height, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < errorPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(warningPosition, y, peakPosition - warningPosition, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(peakPosition, y, errorPosition - peakPosition, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < maximumPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(errorPosition, y, peakPosition - errorPosition, height, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - painter.fillRect(peakPosition, y, maximumPosition - peakPosition, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else { - if (!clipping) { - QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, [&]() { clipping = false; }); - clipping = true; - } - - int end = errorLength + warningLength + nominalLength; - painter.fillRect(minimumPosition, y, end, height, - QBrush(muted ? foregroundErrorColorDisabled : foregroundErrorColor)); - } - - if (peakHoldPosition - 3 < minimumPosition) - ; // Peak-hold below minimum, no drawing. - else if (peakHoldPosition < warningPosition) - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - else if (peakHoldPosition < errorPosition) - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - else - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - - if (magnitudePosition - 3 >= minimumPosition) - painter.fillRect(magnitudePosition - 3, y, 3, height, magnitudeColor); -} - -void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold) -{ - qreal scale = height / minimumLevel; - - QMutexLocker locker(&dataMutex); - int minimumPosition = y + 0; - int maximumPosition = y + height; - int magnitudePosition = y + height - convertToInt(magnitude * scale); - int peakPosition = y + height - convertToInt(peak * scale); - int peakHoldPosition = y + height - convertToInt(peakHold * scale); - int warningPosition = y + height - convertToInt(warningLevel * scale); - int errorPosition = y + height - convertToInt(errorLevel * scale); - - int nominalLength = warningPosition - minimumPosition; - int warningLength = errorPosition - warningPosition; - int errorLength = maximumPosition - errorPosition; - locker.unlock(); - - if (clipping) { - peakPosition = maximumPosition; - } - - if (peakPosition < minimumPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < warningPosition) { - painter.fillRect(x, minimumPosition, width, peakPosition - minimumPosition, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, peakPosition, width, warningPosition - peakPosition, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < errorPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, warningPosition, width, peakPosition - warningPosition, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(x, peakPosition, width, errorPosition - peakPosition, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < maximumPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(x, errorPosition, width, peakPosition - errorPosition, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - painter.fillRect(x, peakPosition, width, maximumPosition - peakPosition, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else { - if (!clipping) { - QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, [&]() { clipping = false; }); - clipping = true; - } - - int end = errorLength + warningLength + nominalLength; - painter.fillRect(x, minimumPosition, width, end, - QBrush(muted ? foregroundErrorColorDisabled : foregroundErrorColor)); - } - - if (peakHoldPosition - 3 < minimumPosition) - ; // Peak-hold below minimum, no drawing. - else if (peakHoldPosition < warningPosition) - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - else if (peakHoldPosition < errorPosition) - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - else - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - - if (magnitudePosition - 3 >= minimumPosition) - painter.fillRect(x, magnitudePosition - 3, width, 3, magnitudeColor); -} - -void VolumeMeter::paintEvent(QPaintEvent *event) -{ - uint64_t ts = os_gettime_ns(); - qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; - calculateBallistics(ts, timeSinceLastRedraw); - bool idle = detectIdle(ts); - - QRect widgetRect = rect(); - int width = widgetRect.width(); - int height = widgetRect.height(); - - QPainter painter(this); - - // Paint window background color (as widget is opaque) - QColor background = palette().color(QPalette::ColorRole::Window); - painter.fillRect(event->region().boundingRect(), background); - - if (vertical) - height -= METER_PADDING * 2; - - // timerEvent requests update of the bar(s) only, so we can avoid the - // overhead of repainting the scale and labels. - if (event->region().boundingRect() != getBarRect()) { - if (needLayoutChange()) - doLayout(); - - if (vertical) { - paintVTicks(painter, displayNrAudioChannels * (meterThickness + 1) - 1, 0, - height - (INDICATOR_THICKNESS + 3)); - } else { - paintHTicks(painter, INDICATOR_THICKNESS + 3, displayNrAudioChannels * (meterThickness + 1) - 1, - width - (INDICATOR_THICKNESS + 3)); - } - } - - if (vertical) { - // Invert the Y axis to ease the math - painter.translate(0, height + METER_PADDING); - painter.scale(1, -1); - } - - for (int channelNr = 0; channelNr < displayNrAudioChannels; channelNr++) { - - int channelNrFixed = (displayNrAudioChannels == 1 && channels > 2) ? 2 : channelNr; - - if (vertical) - paintVMeter(painter, channelNr * (meterThickness + 1), INDICATOR_THICKNESS + 2, meterThickness, - height - (INDICATOR_THICKNESS + 2), displayMagnitude[channelNrFixed], - displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); - else - paintHMeter(painter, INDICATOR_THICKNESS + 2, channelNr * (meterThickness + 1), - width - (INDICATOR_THICKNESS + 2), meterThickness, displayMagnitude[channelNrFixed], - displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); - - if (idle) - continue; - - // By not drawing the input meter boxes the user can - // see that the audio stream has been stopped, without - // having too much visual impact. - if (vertical) - paintInputMeter(painter, channelNr * (meterThickness + 1), 0, meterThickness, - INDICATOR_THICKNESS, displayInputPeakHold[channelNrFixed]); - else - paintInputMeter(painter, 0, channelNr * (meterThickness + 1), INDICATOR_THICKNESS, - meterThickness, displayInputPeakHold[channelNrFixed]); - } - - lastRedrawTime = ts; -} - -QRect VolumeMeter::getBarRect() const -{ - QRect rec = rect(); - if (vertical) - rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1); - else - rec.setHeight(displayNrAudioChannels * (meterThickness + 1) - 1); - - return rec; -} - -void VolumeMeter::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::StyleChange) - recalculateLayout = true; - - QWidget::changeEvent(e); -} - -void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) -{ - volumeMeters.push_back(meter); -} - -void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter) -{ - volumeMeters.removeOne(meter); -} - -void VolumeMeterTimer::timerEvent(QTimerEvent *) -{ - for (VolumeMeter *meter : volumeMeters) { - if (meter->needLayoutChange()) { - // Tell paintEvent to update layout and paint everything - meter->update(); - } else { - // Tell paintEvent to paint only the bars - meter->update(meter->getBarRect()); - } - } -} +#include "moc_VolumeSlider.cpp" VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent) : AbsoluteSlider(parent) { @@ -1447,61 +78,3 @@ void VolumeSlider::paintEvent(QPaintEvent *event) QSlider::paintEvent(event); } - -VolumeAccessibleInterface::VolumeAccessibleInterface(QWidget *w) : QAccessibleWidget(w) {} - -VolumeSlider *VolumeAccessibleInterface::slider() const -{ - return qobject_cast(object()); -} - -QString VolumeAccessibleInterface::text(QAccessible::Text t) const -{ - if (slider()->isVisible()) { - switch (t) { - case QAccessible::Text::Value: - return currentValue().toString(); - default: - break; - } - } - return QAccessibleWidget::text(t); -} - -QVariant VolumeAccessibleInterface::currentValue() const -{ - QString text; - float db = obs_fader_get_db(slider()->fad); - - if (db < -96.0f) - text = "-inf dB"; - else - text = QString::number(db, 'f', 1).append(" dB"); - - return text; -} - -void VolumeAccessibleInterface::setCurrentValue(const QVariant &value) -{ - slider()->setValue(value.toInt()); -} - -QVariant VolumeAccessibleInterface::maximumValue() const -{ - return slider()->maximum(); -} - -QVariant VolumeAccessibleInterface::minimumValue() const -{ - return slider()->minimum(); -} - -QVariant VolumeAccessibleInterface::minimumStepSize() const -{ - return slider()->singleStep(); -} - -QAccessible::Role VolumeAccessibleInterface::role() const -{ - return QAccessible::Role::Slider; -} diff --git a/frontend/components/VolumeSlider.hpp b/frontend/components/VolumeSlider.hpp index 51c77f247..10b794889 100644 --- a/frontend/components/VolumeSlider.hpp +++ b/frontend/components/VolumeSlider.hpp @@ -1,303 +1,8 @@ #pragma once +#include + #include -#include -#include -#include -#include -#include -#include -#include -#include "absolute-slider.hpp" - -class QPushButton; -class VolumeMeterTimer; -class VolumeSlider; - -class VolumeMeter : public QWidget { - Q_OBJECT - Q_PROPERTY(QColor backgroundNominalColor READ getBackgroundNominalColor WRITE setBackgroundNominalColor - DESIGNABLE true) - Q_PROPERTY(QColor backgroundWarningColor READ getBackgroundWarningColor WRITE setBackgroundWarningColor - DESIGNABLE true) - Q_PROPERTY( - QColor backgroundErrorColor READ getBackgroundErrorColor WRITE setBackgroundErrorColor DESIGNABLE true) - Q_PROPERTY(QColor foregroundNominalColor READ getForegroundNominalColor WRITE setForegroundNominalColor - DESIGNABLE true) - Q_PROPERTY(QColor foregroundWarningColor READ getForegroundWarningColor WRITE setForegroundWarningColor - DESIGNABLE true) - Q_PROPERTY( - QColor foregroundErrorColor READ getForegroundErrorColor WRITE setForegroundErrorColor DESIGNABLE true) - - Q_PROPERTY(QColor backgroundNominalColorDisabled READ getBackgroundNominalColorDisabled WRITE - setBackgroundNominalColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor backgroundWarningColorDisabled READ getBackgroundWarningColorDisabled WRITE - setBackgroundWarningColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor backgroundErrorColorDisabled READ getBackgroundErrorColorDisabled WRITE - setBackgroundErrorColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundNominalColorDisabled READ getForegroundNominalColorDisabled WRITE - setForegroundNominalColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundWarningColorDisabled READ getForegroundWarningColorDisabled WRITE - setForegroundWarningColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundErrorColorDisabled READ getForegroundErrorColorDisabled WRITE - setForegroundErrorColorDisabled DESIGNABLE true) - - Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor DESIGNABLE true) - Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE setMagnitudeColor DESIGNABLE true) - Q_PROPERTY(QColor majorTickColor READ getMajorTickColor WRITE setMajorTickColor DESIGNABLE true) - Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE setMinorTickColor DESIGNABLE true) - Q_PROPERTY(int meterThickness READ getMeterThickness WRITE setMeterThickness DESIGNABLE true) - Q_PROPERTY(qreal meterFontScaling READ getMeterFontScaling WRITE setMeterFontScaling DESIGNABLE true) - - // Levels are denoted in dBFS. - Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel DESIGNABLE true) - Q_PROPERTY(qreal warningLevel READ getWarningLevel WRITE setWarningLevel DESIGNABLE true) - Q_PROPERTY(qreal errorLevel READ getErrorLevel WRITE setErrorLevel DESIGNABLE true) - Q_PROPERTY(qreal clipLevel READ getClipLevel WRITE setClipLevel DESIGNABLE true) - Q_PROPERTY(qreal minimumInputLevel READ getMinimumInputLevel WRITE setMinimumInputLevel DESIGNABLE true) - - // Rates are denoted in dB/second. - Q_PROPERTY(qreal peakDecayRate READ getPeakDecayRate WRITE setPeakDecayRate DESIGNABLE true) - - // Time in seconds for the VU meter to integrate over. - Q_PROPERTY(qreal magnitudeIntegrationTime READ getMagnitudeIntegrationTime WRITE setMagnitudeIntegrationTime - DESIGNABLE true) - - // Duration is denoted in seconds. - Q_PROPERTY(qreal peakHoldDuration READ getPeakHoldDuration WRITE setPeakHoldDuration DESIGNABLE true) - Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration WRITE setInputPeakHoldDuration - DESIGNABLE true) - - friend class VolControl; - -private: - obs_volmeter_t *obs_volmeter; - static std::weak_ptr updateTimer; - std::shared_ptr updateTimerRef; - - inline void resetLevels(); - inline void doLayout(); - inline bool detectIdle(uint64_t ts); - inline void calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw = 0.0); - inline void calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw); - - inline int convertToInt(float number); - void paintInputMeter(QPainter &painter, int x, int y, int width, int height, float peakHold); - void paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold); - void paintHTicks(QPainter &painter, int x, int y, int width); - void paintVMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold); - void paintVTicks(QPainter &painter, int x, int y, int height); - - QMutex dataMutex; - - bool recalculateLayout = true; - uint64_t currentLastUpdateTime = 0; - float currentMagnitude[MAX_AUDIO_CHANNELS]; - float currentPeak[MAX_AUDIO_CHANNELS]; - float currentInputPeak[MAX_AUDIO_CHANNELS]; - - int displayNrAudioChannels = 0; - float displayMagnitude[MAX_AUDIO_CHANNELS]; - float displayPeak[MAX_AUDIO_CHANNELS]; - float displayPeakHold[MAX_AUDIO_CHANNELS]; - uint64_t displayPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; - float displayInputPeakHold[MAX_AUDIO_CHANNELS]; - uint64_t displayInputPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; - - QFont tickFont; - QColor backgroundNominalColor; - QColor backgroundWarningColor; - QColor backgroundErrorColor; - QColor foregroundNominalColor; - QColor foregroundWarningColor; - QColor foregroundErrorColor; - - QColor backgroundNominalColorDisabled; - QColor backgroundWarningColorDisabled; - QColor backgroundErrorColorDisabled; - QColor foregroundNominalColorDisabled; - QColor foregroundWarningColorDisabled; - QColor foregroundErrorColorDisabled; - - QColor clipColor; - QColor magnitudeColor; - QColor majorTickColor; - QColor minorTickColor; - - int meterThickness; - qreal meterFontScaling; - - qreal minimumLevel; - qreal warningLevel; - qreal errorLevel; - qreal clipLevel; - qreal minimumInputLevel; - qreal peakDecayRate; - qreal magnitudeIntegrationTime; - qreal peakHoldDuration; - qreal inputPeakHoldDuration; - - QColor p_backgroundNominalColor; - QColor p_backgroundWarningColor; - QColor p_backgroundErrorColor; - QColor p_foregroundNominalColor; - QColor p_foregroundWarningColor; - QColor p_foregroundErrorColor; - - uint64_t lastRedrawTime = 0; - int channels = 0; - bool clipping = false; - bool vertical; - bool muted = false; - -public: - explicit VolumeMeter(QWidget *parent = nullptr, obs_volmeter_t *obs_volmeter = nullptr, bool vertical = false); - ~VolumeMeter(); - - void setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], - const float inputPeak[MAX_AUDIO_CHANNELS]); - QRect getBarRect() const; - bool needLayoutChange(); - - QColor getBackgroundNominalColor() const; - void setBackgroundNominalColor(QColor c); - QColor getBackgroundWarningColor() const; - void setBackgroundWarningColor(QColor c); - QColor getBackgroundErrorColor() const; - void setBackgroundErrorColor(QColor c); - QColor getForegroundNominalColor() const; - void setForegroundNominalColor(QColor c); - QColor getForegroundWarningColor() const; - void setForegroundWarningColor(QColor c); - QColor getForegroundErrorColor() const; - void setForegroundErrorColor(QColor c); - - QColor getBackgroundNominalColorDisabled() const; - void setBackgroundNominalColorDisabled(QColor c); - QColor getBackgroundWarningColorDisabled() const; - void setBackgroundWarningColorDisabled(QColor c); - QColor getBackgroundErrorColorDisabled() const; - void setBackgroundErrorColorDisabled(QColor c); - QColor getForegroundNominalColorDisabled() const; - void setForegroundNominalColorDisabled(QColor c); - QColor getForegroundWarningColorDisabled() const; - void setForegroundWarningColorDisabled(QColor c); - QColor getForegroundErrorColorDisabled() const; - void setForegroundErrorColorDisabled(QColor c); - - QColor getClipColor() const; - void setClipColor(QColor c); - QColor getMagnitudeColor() const; - void setMagnitudeColor(QColor c); - QColor getMajorTickColor() const; - void setMajorTickColor(QColor c); - QColor getMinorTickColor() const; - void setMinorTickColor(QColor c); - int getMeterThickness() const; - void setMeterThickness(int v); - qreal getMeterFontScaling() const; - void setMeterFontScaling(qreal v); - qreal getMinimumLevel() const; - void setMinimumLevel(qreal v); - qreal getWarningLevel() const; - void setWarningLevel(qreal v); - qreal getErrorLevel() const; - void setErrorLevel(qreal v); - qreal getClipLevel() const; - void setClipLevel(qreal v); - qreal getMinimumInputLevel() const; - void setMinimumInputLevel(qreal v); - qreal getPeakDecayRate() const; - void setPeakDecayRate(qreal v); - qreal getMagnitudeIntegrationTime() const; - void setMagnitudeIntegrationTime(qreal v); - qreal getPeakHoldDuration() const; - void setPeakHoldDuration(qreal v); - qreal getInputPeakHoldDuration() const; - void setInputPeakHoldDuration(qreal v); - void setPeakMeterType(enum obs_peak_meter_type peakMeterType); - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void wheelEvent(QWheelEvent *event) override; - -protected: - void paintEvent(QPaintEvent *event) override; - void changeEvent(QEvent *e) override; -}; - -class VolumeMeterTimer : public QTimer { - Q_OBJECT - -public: - inline VolumeMeterTimer() : QTimer() {} - - void AddVolControl(VolumeMeter *meter); - void RemoveVolControl(VolumeMeter *meter); - -protected: - void timerEvent(QTimerEvent *event) override; - QList volumeMeters; -}; - -class QLabel; -class VolumeSlider; -class MuteCheckBox; -class OBSSourceLabel; - -class VolControl : public QFrame { - Q_OBJECT - -private: - OBSSource source; - std::vector sigs; - OBSSourceLabel *nameLabel; - QLabel *volLabel; - VolumeMeter *volMeter; - VolumeSlider *slider; - MuteCheckBox *mute; - QPushButton *config = nullptr; - float levelTotal; - float levelCount; - OBSFader obs_fader; - OBSVolMeter obs_volmeter; - bool vertical; - QMenu *contextMenu; - - static void OBSVolumeChanged(void *param, float db); - static void OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], - const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]); - static void OBSVolumeMuted(void *data, calldata_t *calldata); - static void OBSMixersOrMonitoringChanged(void *data, calldata_t *); - - void EmitConfigClicked(); - -private slots: - void VolumeChanged(); - void VolumeMuted(bool muted); - void MixersOrMonitoringChanged(); - - void SetMuted(bool checked); - void SliderChanged(int vol); - void updateText(); - -signals: - void ConfigClicked(); - -public: - explicit VolControl(OBSSource source, bool showConfig = false, bool vertical = false); - ~VolControl(); - - inline obs_source_t *GetSource() const { return source; } - - void SetMeterDecayRate(qreal q); - void setPeakMeterType(enum obs_peak_meter_type peakMeterType); - - void EnableSlider(bool enable); - inline void SetContextMenu(QMenu *cm) { contextMenu = cm; } - - void refreshColors(); -}; class VolumeSlider : public AbsoluteSlider { Q_OBJECT @@ -318,24 +23,3 @@ private: protected: virtual void paintEvent(QPaintEvent *event) override; }; - -class VolumeAccessibleInterface : public QAccessibleWidget { - -public: - VolumeAccessibleInterface(QWidget *w); - - QVariant currentValue() const; - void setCurrentValue(const QVariant &value); - - QVariant maximumValue() const; - QVariant minimumValue() const; - - QVariant minimumStepSize() const; - -private: - VolumeSlider *slider() const; - -protected: - virtual QAccessible::Role role() const override; - virtual QString text(QAccessible::Text t) const override; -}; diff --git a/frontend/utility/AdvancedOutput.cpp b/frontend/utility/AdvancedOutput.cpp index 30d203174..0d927721d 100644 --- a/frontend/utility/AdvancedOutput.cpp +++ b/frontend/utility/AdvancedOutput.cpp @@ -1,1453 +1,13 @@ -#include -#include -#include -#include -#include +#include "AdvancedOutput.hpp" + +#include +#include +#include + #include -#include "audio-encoders.hpp" -#include "multitrack-video-error.hpp" -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" -#include "window-basic-vcam.hpp" using namespace std; -extern bool EncoderAvailable(const char *encoder); - -volatile bool streaming_active = false; -volatile bool recording_active = false; -volatile bool recording_paused = false; -volatile bool replaybuf_active = false; -volatile bool virtualcam_active = false; - -#define RTMP_PROTOCOL "rtmp" -#define SRT_PROTOCOL "srt" -#define RIST_PROTOCOL "rist" - -static void OBSStreamStarting(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - return; - - output->delayActive = true; - QMetaObject::invokeMethod(output->main, "StreamDelayStarting", Q_ARG(int, sec)); -} - -static void OBSStreamStopping(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - QMetaObject::invokeMethod(output->main, "StreamStopping"); - else - QMetaObject::invokeMethod(output->main, "StreamDelayStopping", Q_ARG(int, sec)); -} - -static void OBSStartStreaming(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->streamingActive = true; - os_atomic_set_bool(&streaming_active, true); - QMetaObject::invokeMethod(output->main, "StreamingStart"); -} - -static void OBSStopStreaming(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->streamingActive = false; - output->delayActive = false; - output->multitrackVideoActive = false; - os_atomic_set_bool(&streaming_active, false); - QMetaObject::invokeMethod(output->main, "StreamingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSStartRecording(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->recordingActive = true; - os_atomic_set_bool(&recording_active, true); - QMetaObject::invokeMethod(output->main, "RecordingStart"); -} - -static void OBSStopRecording(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->recordingActive = false; - os_atomic_set_bool(&recording_active, false); - os_atomic_set_bool(&recording_paused, false); - QMetaObject::invokeMethod(output->main, "RecordingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSRecordStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "RecordStopping"); -} - -static void OBSRecordFileChanged(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - const char *next_file = calldata_string(params, "next_file"); - - QString arg_last_file = QString::fromUtf8(output->lastRecordingPath.c_str()); - - QMetaObject::invokeMethod(output->main, "RecordingFileChanged", Q_ARG(QString, arg_last_file)); - - output->lastRecordingPath = next_file; -} - -static void OBSStartReplayBuffer(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->replayBufferActive = true; - os_atomic_set_bool(&replaybuf_active, true); - QMetaObject::invokeMethod(output->main, "ReplayBufferStart"); -} - -static void OBSStopReplayBuffer(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->replayBufferActive = false; - os_atomic_set_bool(&replaybuf_active, false); - QMetaObject::invokeMethod(output->main, "ReplayBufferStop", Q_ARG(int, code)); -} - -static void OBSReplayBufferStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferStopping"); -} - -static void OBSReplayBufferSaved(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferSaved", Qt::QueuedConnection); -} - -static void OBSStartVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->virtualCamActive = true; - os_atomic_set_bool(&virtualcam_active, true); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStart"); -} - -static void OBSStopVirtualCam(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->virtualCamActive = false; - os_atomic_set_bool(&virtualcam_active, false); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", Q_ARG(int, code)); -} - -static void OBSDeactivateVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->DestroyVirtualCamView(); -} - -/* ------------------------------------------------------------------------ */ - -struct StartMultitrackVideoStreamingGuard { - StartMultitrackVideoStreamingGuard() { future = guard.get_future().share(); }; - ~StartMultitrackVideoStreamingGuard() { guard.set_value(); } - - std::shared_future GetFuture() const { return future; } - - static std::shared_future MakeReadyFuture() - { - StartMultitrackVideoStreamingGuard guard; - return guard.GetFuture(); - } - -private: - std::promise guard; - std::shared_future future; -}; - -/* ------------------------------------------------------------------------ */ - -static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleAACEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static bool CreateSimpleOpusEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleOpusEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, - const char *prot_test2 = nullptr) -{ - return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && - (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; -} - -static bool return_first_id(void *data, const char *id) -{ - const char **output = (const char **)data; - - *output = id; - return false; -} - -static const char *GetStreamOutputType(const obs_service_t *service) -{ - const char *protocol = obs_service_get_protocol(service); - const char *output = nullptr; - - if (!protocol) { - blog(LOG_WARNING, "The service '%s' has no protocol set", obs_service_get_id(service)); - return nullptr; - } - - if (!obs_is_output_protocol_registered(protocol)) { - blog(LOG_WARNING, "The protocol '%s' is not registered", protocol); - return nullptr; - } - - /* Check if the service has a preferred output type */ - output = obs_service_get_preferred_output_type(service); - if (output) { - if ((obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0) - return output; - - blog(LOG_WARNING, "The output '%s' is not registered, fallback to another one", output); - } - - /* Otherwise, prefer first-party output types */ - if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) { - return "rtmp_output"; - } else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) { - return "ffmpeg_hls_muxer"; - } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", "RIST")) { - return "ffmpeg_mpegts_muxer"; - } - - /* If third-party protocol, use the first enumerated type */ - obs_enum_output_types_with_protocol(protocol, &output, return_first_id); - if (output) - return output; - - blog(LOG_WARNING, "No output compatible with the service '%s' is registered", obs_service_get_id(service)); - - return nullptr; -} - -/* ------------------------------------------------------------------------ */ - -inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) -{ - if (main->vcamEnabled) { - virtualCam = obs_output_create(VIRTUAL_CAM_ID, "virtualcam_output", nullptr, nullptr); - - signal_handler_t *signal = obs_output_get_signal_handler(virtualCam); - startVirtualCam.Connect(signal, "start", OBSStartVirtualCam, this); - stopVirtualCam.Connect(signal, "stop", OBSStopVirtualCam, this); - deactivateVirtualCam.Connect(signal, "deactivate", OBSDeactivateVirtualCam, this); - } - - auto multitrack_enabled = config_get_bool(main->Config(), "Stream1", "EnableMultitrackVideo"); - if (!config_has_user_value(main->Config(), "Stream1", "EnableMultitrackVideo")) { - auto service = main_->GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - multitrack_enabled = obs_data_has_user_value(settings, "multitrack_video_configuration_url"); - } - if (multitrack_enabled) - multitrackVideo = make_unique(); -} - -extern void log_vcam_changed(const VCamConfig &config, bool starting); - -bool BasicOutputHandler::StartVirtualCam() -{ - if (!main->vcamEnabled) - return false; - - bool typeIsProgram = main->vcamConfig.type == VCamOutputType::ProgramView; - - if (!virtualCamView && !typeIsProgram) - virtualCamView = obs_view_create(); - - UpdateVirtualCamOutputSource(); - - if (!virtualCamVideo) { - virtualCamVideo = typeIsProgram ? obs_get_video() : obs_view_add(virtualCamView); - - if (!virtualCamVideo) - return false; - } - - obs_output_set_media(virtualCam, virtualCamVideo, obs_get_audio()); - if (!Active()) - SetupOutputs(); - - bool success = obs_output_start(virtualCam); - if (!success) { - QString errorReason; - - const char *error = obs_output_get_last_error(virtualCam); - if (error) { - errorReason = QT_UTF8(error); - } else { - errorReason = QTStr("Output.StartFailedGeneric"); - } - - QMessageBox::critical(main, QTStr("Output.StartVirtualCamFailed"), errorReason); - - DestroyVirtualCamView(); - } - - log_vcam_changed(main->vcamConfig, true); - - return success; -} - -void BasicOutputHandler::StopVirtualCam() -{ - if (main->vcamEnabled) { - obs_output_stop(virtualCam); - } -} - -bool BasicOutputHandler::VirtualCamActive() const -{ - if (main->vcamEnabled) { - return obs_output_active(virtualCam); - } - return false; -} - -void BasicOutputHandler::UpdateVirtualCamOutputSource() -{ - if (!main->vcamEnabled || !virtualCamView) - return; - - OBSSourceAutoRelease source; - - switch (main->vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - DestroyVirtualCameraScene(); - return; - case VCamOutputType::PreviewOutput: { - DestroyVirtualCameraScene(); - OBSSource s = main->GetCurrentSceneSource(); - obs_source_get_ref(s); - source = s.Get(); - break; - } - case VCamOutputType::SceneOutput: - DestroyVirtualCameraScene(); - source = obs_get_source_by_name(main->vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - OBSSourceAutoRelease s = obs_get_source_by_name(main->vcamConfig.source.c_str()); - - if (!vCamSourceScene) - vCamSourceScene = obs_scene_create_private("vcam_source"); - source = obs_source_get_ref(obs_scene_get_source(vCamSourceScene)); - - if (vCamSourceSceneItem && (obs_sceneitem_get_source(vCamSourceSceneItem) != s)) { - obs_sceneitem_remove(vCamSourceSceneItem); - vCamSourceSceneItem = nullptr; - } - - if (!vCamSourceSceneItem) { - vCamSourceSceneItem = obs_scene_add(vCamSourceScene, s); - - obs_sceneitem_set_bounds_type(vCamSourceSceneItem, OBS_BOUNDS_SCALE_INNER); - obs_sceneitem_set_bounds_alignment(vCamSourceSceneItem, OBS_ALIGN_CENTER); - - const struct vec2 size = { - (float)obs_source_get_width(source), - (float)obs_source_get_height(source), - }; - obs_sceneitem_set_bounds(vCamSourceSceneItem, &size); - } - break; - } - - OBSSourceAutoRelease current = obs_view_get_source(virtualCamView, 0); - if (source != current) - obs_view_set_source(virtualCamView, 0, source); -} - -void BasicOutputHandler::DestroyVirtualCamView() -{ - if (main->vcamConfig.type == VCamOutputType::ProgramView) { - virtualCamVideo = nullptr; - return; - } - - obs_view_remove(virtualCamView); - obs_view_set_source(virtualCamView, 0, nullptr); - virtualCamVideo = nullptr; - - obs_view_destroy(virtualCamView); - virtualCamView = nullptr; - - DestroyVirtualCameraScene(); -} - -void BasicOutputHandler::DestroyVirtualCameraScene() -{ - if (!vCamSourceScene) - return; - - obs_scene_release(vCamSourceScene); - vCamSourceScene = nullptr; - vCamSourceSceneItem = nullptr; -} - -/* ------------------------------------------------------------------------ */ - -struct SimpleOutput : BasicOutputHandler { - OBSEncoder audioStreaming; - OBSEncoder videoStreaming; - OBSEncoder audioRecording; - OBSEncoder audioArchive; - OBSEncoder videoRecording; - OBSEncoder audioTrack[MAX_AUDIO_MIXES]; - - string videoEncoder; - string videoQuality; - bool usingRecordingPreset = false; - bool recordingConfigured = false; - bool ffmpegOutput = false; - bool lowCPUx264 = false; - - SimpleOutput(OBSBasic *main_); - - int CalcCRF(int crf); - - void UpdateRecordingSettings_x264_crf(int crf); - void UpdateRecordingSettings_qsv11(int crf, bool av1); - void UpdateRecordingSettings_nvenc(int cqp); - void UpdateRecordingSettings_nvenc_hevc_av1(int cqp); - void UpdateRecordingSettings_amd_cqp(int cqp); - void UpdateRecordingSettings_apple(int quality); -#ifdef ENABLE_HEVC - void UpdateRecordingSettings_apple_hevc(int quality); -#endif - void UpdateRecordingSettings(); - void UpdateRecordingAudioSettings(); - virtual void Update() override; - - void SetupOutputs() override; - int GetAudioBitrate() const; - - void LoadRecordingPreset_Lossy(const char *encoder); - void LoadRecordingPreset_Lossless(); - void LoadRecordingPreset(); - - void LoadStreamingPreset_Lossy(const char *encoder); - - void UpdateRecording(); - bool ConfigureRecording(bool useReplayBuffer); - - bool IsVodTrackEnabled(obs_service_t *service); - void SetupVodTrack(obs_service_t *service); - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; -}; - -void SimpleOutput::LoadRecordingPreset_Lossless() -{ - fileOutput = obs_output_create("ffmpeg_output", "simple_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(simple output)"; - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "format_name", "avi"); - obs_data_set_string(settings, "video_encoder", "utvideo"); - obs_data_set_string(settings, "audio_encoder", "pcm_s16le"); - - obs_output_update(fileOutput, settings); -} - -void SimpleOutput::LoadRecordingPreset_Lossy(const char *encoderId) -{ - videoRecording = obs_video_encoder_create(encoderId, "simple_video_recording", nullptr, nullptr); - if (!videoRecording) - throw "Failed to create video recording encoder (simple output)"; - obs_encoder_release(videoRecording); -} - -void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId) -{ - videoStreaming = obs_video_encoder_create(encoderId, "simple_video_stream", nullptr, nullptr); - if (!videoStreaming) - throw "Failed to create video streaming encoder (simple output)"; - obs_encoder_release(videoStreaming); -} - -/* mistakes have been made to lead us to this. */ -const char *get_simple_output_encoder(const char *encoder) -{ - if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - return "obs_qsv11_v2"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - return "obs_qsv11_av1"; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - return "h264_texture_amf"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - return "h265_texture_amf"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - return "av1_texture_amf"; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - return EncoderAvailable("obs_nvenc_h264_tex") ? "obs_nvenc_h264_tex" : "ffmpeg_nvenc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - return EncoderAvailable("obs_nvenc_hevc_tex") ? "obs_nvenc_hevc_tex" : "ffmpeg_hevc_nvenc"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - return "obs_nvenc_av1_tex"; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.avc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.hevc"; -#endif - } - - return "obs_x264"; -} - -void SimpleOutput::LoadRecordingPreset() -{ - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "RecEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "RecAudioEncoder"); - - videoEncoder = encoder; - videoQuality = quality; - ffmpegOutput = false; - - if (strcmp(quality, "Stream") == 0) { - videoRecording = videoStreaming; - audioRecording = audioStreaming; - usingRecordingPreset = false; - return; - - } else if (strcmp(quality, "Lossless") == 0) { - LoadRecordingPreset_Lossless(); - usingRecordingPreset = true; - ffmpegOutput = true; - return; - - } else { - lowCPUx264 = false; - - if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) - lowCPUx264 = true; - LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder)); - usingRecordingPreset = true; - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioRecording, 192, "simple_opus_recording", 0); - else - success = CreateSimpleAACEncoder(audioRecording, 192, "simple_aac_recording", 0); - - if (!success) - throw "Failed to create audio recording encoder " - "(simple output)"; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[23]; - if (strcmp(audio_encoder, "opus") == 0) { - snprintf(name, sizeof name, "simple_opus_recording%d", i); - success = CreateSimpleOpusEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } else { - snprintf(name, sizeof name, "simple_aac_recording%d", i); - success = CreateSimpleAACEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } - if (!success) - throw "Failed to create multi-track audio recording encoder " - "(simple output)"; - } - } -} - -#define SIMPLE_ARCHIVE_NAME "simple_archive_audio" - -SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - - LoadStreamingPreset_Lossy(get_simple_output_encoder(encoder)); - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioStreaming, GetAudioBitrate(), "simple_opus", 0); - else - success = CreateSimpleAACEncoder(audioStreaming, GetAudioBitrate(), "simple_aac", 0); - - if (!success) - throw "Failed to create audio streaming encoder (simple output)"; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - else - success = CreateSimpleAACEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - - if (!success) - throw "Failed to create audio archive encoder (simple output)"; - - LoadRecordingPreset(); - - if (!ffmpegOutput) { - bool useReplayBuffer = config_get_bool(main->Config(), "SimpleOutput", "RecRB"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool use_native = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(use_native ? "mp4_output" : "ffmpeg_muxer", "simple_file_output", - nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(simple output)"; - } - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); -} - -int SimpleOutput::GetAudioBitrate() const -{ - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); - - if (strcmp(audio_encoder, "opus") == 0) - return FindClosestAvailableSimpleOpusBitrate(bitrate); - - return FindClosestAvailableSimpleAACBitrate(bitrate); -} - -void SimpleOutput::Update() -{ - OBSDataAutoRelease videoSettings = obs_data_create(); - OBSDataAutoRelease audioSettings = obs_data_create(); - - int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); - int audioBitrate = GetAudioBitrate(); - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - const char *custom = config_get_string(main->Config(), "SimpleOutput", "x264Settings"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *encoder_id = obs_encoder_get_id(videoStreaming); - const char *presetType; - const char *preset; - - if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - presetType = "AMDPreset"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset2"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - presetType = "NVENCPreset2"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - presetType = "AMDAV1Preset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - presetType = "NVENCPreset2"; - - } else { - presetType = "Preset"; - } - - preset = config_get_string(main->Config(), "SimpleOutput", presetType); - - /* Only use preset2 for legacy/FFmpeg NVENC Encoder. */ - if (strncmp(encoder_id, "ffmpeg_", 7) == 0 && strcmp(presetType, "NVENCPreset2") == 0) { - obs_data_set_string(videoSettings, "preset2", preset); - } else { - obs_data_set_string(videoSettings, "preset", preset); - } - - obs_data_set_string(videoSettings, "rate_control", "CBR"); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - - if (advanced) - obs_data_set_string(videoSettings, "x264opts", custom); - - obs_data_set_string(audioSettings, "rate_control", "CBR"); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - - obs_service_apply_encoder_settings(main->GetService(), videoSettings, audioSettings); - - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - } - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, videoSettings); - obs_encoder_update(audioStreaming, audioSettings); - obs_encoder_update(audioArchive, audioSettings); -} - -void SimpleOutput::UpdateRecordingAudioSettings() -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", 192); - obs_data_set_string(settings, "rate_control", "CBR"); - - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv || strcmp(quality, "Stream") == 0) { - obs_encoder_update(audioRecording, settings); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_update(audioTrack[i], settings); - } - } - } -} - -#define CROSS_DIST_CUTOFF 2000.0 - -int SimpleOutput::CalcCRF(int crf) -{ - int cx = config_get_uint(main->Config(), "Video", "OutputCX"); - int cy = config_get_uint(main->Config(), "Video", "OutputCY"); - double fCX = double(cx); - double fCY = double(cy); - - if (lowCPUx264) - crf -= 2; - - double crossDist = sqrt(fCX * fCX + fCY * fCY); - double crfResReduction = fmin(CROSS_DIST_CUTOFF, crossDist) / CROSS_DIST_CUTOFF; - crfResReduction = (1.0 - crfResReduction) * 10.0; - - return crf - int(crfResReduction); -} - -void SimpleOutput::UpdateRecordingSettings_x264_crf(int crf) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "crf", crf); - obs_data_set_bool(settings, "use_bufsize", true); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", lowCPUx264 ? "ultrafast" : "veryfast"); - - obs_encoder_update(videoRecording, settings); -} - -static bool icq_available(obs_encoder_t *encoder) -{ - obs_properties_t *props = obs_encoder_properties(encoder); - obs_property_t *p = obs_properties_get(props, "rate_control"); - bool icq_found = false; - - size_t num = obs_property_list_item_count(p); - for (size_t i = 0; i < num; i++) { - const char *val = obs_property_list_item_string(p, i); - if (strcmp(val, "ICQ") == 0) { - icq_found = true; - break; - } - } - - obs_properties_destroy(props); - return icq_found; -} - -void SimpleOutput::UpdateRecordingSettings_qsv11(int crf, bool av1) -{ - bool icq = icq_available(videoRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "profile", "high"); - - if (icq && !av1) { - obs_data_set_string(settings, "rate_control", "ICQ"); - obs_data_set_int(settings, "icq_quality", crf); - } else { - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_int(settings, "cqp", crf); - } - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc_hevc_av1(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_apple(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} - -#ifdef ENABLE_HEVC -void SimpleOutput::UpdateRecordingSettings_apple_hevc(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} -#endif - -void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", "quality"); - obs_data_set_int(settings, "cqp", cqp); - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings() -{ - bool ultra_hq = (videoQuality == "HQ"); - int crf = CalcCRF(ultra_hq ? 16 : 23); - - if (astrcmp_n(videoEncoder.c_str(), "x264", 4) == 0) { - UpdateRecordingSettings_x264_crf(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV) { - UpdateRecordingSettings_qsv11(crf, false); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV_AV1) { - UpdateRecordingSettings_qsv11(crf, true); - - } else if (videoEncoder == SIMPLE_ENCODER_AMD) { - UpdateRecordingSettings_amd_cqp(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_AMD_HEVC) { - UpdateRecordingSettings_amd_cqp(crf); -#endif - - } else if (videoEncoder == SIMPLE_ENCODER_AMD_AV1) { - UpdateRecordingSettings_amd_cqp(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_NVENC) { - UpdateRecordingSettings_nvenc(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_HEVC) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); -#endif - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_AV1) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_H264) { - /* These are magic numbers. 0 - 100, more is better. */ - UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_HEVC) { - UpdateRecordingSettings_apple_hevc(ultra_hq ? 70 : 50); -#endif - } - UpdateRecordingAudioSettings(); -} - -inline void SimpleOutput::SetupOutputs() -{ - SimpleOutput::Update(); - obs_encoder_set_video(videoStreaming, obs_get_video()); - obs_encoder_set_audio(audioStreaming, obs_get_audio()); - obs_encoder_set_audio(audioArchive, obs_get_audio()); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (usingRecordingPreset) { - if (ffmpegOutput) { - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - } else { - obs_encoder_set_video(videoRecording, obs_get_video()); - if (flv) { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_set_audio(audioTrack[i], obs_get_audio()); - } - } - } - } - } else { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } -} - -const char *FindAudioEncoderFromCodec(const char *type) -{ - const char *alt_enc_id = nullptr; - size_t i = 0; - - while (obs_enum_encoder_types(i++, &alt_enc_id)) { - const char *codec = obs_get_encoder_codec(alt_enc_id); - if (strcmp(type, codec) == 0) { - return alt_enc_id; - } - } - - return nullptr; -} - -std::shared_future SimpleOutput::SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation) -{ - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - auto audio_bitrate = GetAudioBitrate(); - auto vod_track_mixer = IsVodTrackEnabled(service) ? std::optional{1} : std::nullopt; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "simple_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, audioStreaming, 0); - obs_output_set_service(streamOutput, service); - return true; - }; - - return SetupMultitrackVideo(service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0, vod_track_mixer, - [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -static inline bool ServiceSupportsVodTrack(const char *service); - -static void clear_archive_encoder(obs_output_t *output, const char *expected_name) -{ - obs_encoder_t *last = obs_output_get_audio_encoder(output, 1); - bool clear = false; - - /* ensures that we don't remove twitch's soundtrack encoder */ - if (last) { - const char *name = obs_encoder_get_name(last); - clear = name && strcmp(name, expected_name) == 0; - obs_encoder_release(last); - } - - if (clear) - obs_output_set_audio_encoder(output, nullptr, 1); -} - -bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service) -{ - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enable = config_get_bool(main->Config(), "SimpleOutput", "VodTrackEnabled"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *name = obs_data_get_string(settings, "service"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) - return enableForCustomServer ? enable : false; - else - return advanced && enable && ServiceSupportsVodTrack(name); -} - -void SimpleOutput::SetupVodTrack(obs_service_t *service) -{ - if (IsVodTrackEnabled(service)) - obs_output_set_audio_encoder(streamOutput, audioArchive, 1); - else - clear_archive_encoder(streamOutput, SIMPLE_ARCHIVE_NAME); -} - -bool SimpleOutput::StartStreaming(obs_service_t *service) -{ - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_uint(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_uint(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - - if (!multitrackVideo || !multitrackVideoActive) - SetupVodTrack(service); - - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -void SimpleOutput::UpdateRecording() -{ - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - int idx = 0; - int idx2 = 0; - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - - if (replayBufferActive || recordingActive) - return; - - if (usingRecordingPreset) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(streamOutput)) { - Update(); - } - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput) { - obs_output_set_video_encoder(fileOutput, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(fileOutput, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, audioTrack[i], idx++); - } - } - } - } - if (replayBuffer) { - obs_output_set_video_encoder(replayBuffer, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(replayBuffer, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(replayBuffer, audioTrack[i], idx2++); - } - } - } - } - - recordingConfigured = true; -} - -bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) -{ - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - const char *format = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *mux = config_get_string(main->Config(), "SimpleOutput", "MuxerCustom"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - int rbTime = config_get_int(main->Config(), "SimpleOutput", "RecRBTime"); - int rbSize = config_get_int(main->Config(), "SimpleOutput", "RecRBSize"); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - - bool is_fragmented = strncmp(format, "fragmented", 10) == 0; - bool is_lossless = videoQuality == "Lossless"; - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - if (updateReplayBuffer) { - f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(format); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usingRecordingPreset ? rbSize : 0); - } else { - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, ffmpegOutput ? "avi" : format, noSpace, overwriteIfExists, - f.c_str(), ffmpegOutput); - obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str()); - if (ffmpegOutput) - obs_output_set_mixers(fileOutput, tracks); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && !is_lossless && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented && !is_lossless) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - if (updateReplayBuffer) - obs_output_update(replayBuffer, settings); - else - obs_output_update(fileOutput, settings); - - return true; -} - -bool SimpleOutput::StartRecording() -{ - UpdateRecording(); - if (!ConfigureRecording(false)) - return false; - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool SimpleOutput::StartReplayBuffer() -{ - UpdateRecording(); - if (!ConfigureRecording(true)) - return false; - if (!obs_output_start(replayBuffer)) { - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), QTStr("Output.StartFailedGeneric")); - return false; - } - - return true; -} - -void SimpleOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void SimpleOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void SimpleOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool SimpleOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool SimpleOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool SimpleOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - -struct AdvancedOutput : BasicOutputHandler { - OBSEncoder streamAudioEnc; - OBSEncoder streamArchiveEnc; - OBSEncoder streamTrack[MAX_AUDIO_MIXES]; - OBSEncoder recordTrack[MAX_AUDIO_MIXES]; - OBSEncoder videoStreaming; - OBSEncoder videoRecording; - - bool ffmpegOutput; - bool ffmpegRecording; - bool useStreamEncoder; - bool useStreamAudioEncoder; - bool usesBitrate = false; - - AdvancedOutput(OBSBasic *main_); - - inline void UpdateStreamSettings(); - inline void UpdateRecordingSettings(); - inline void UpdateAudioSettings(); - virtual void Update() override; - - inline std::optional VodTrackMixerIdx(obs_service_t *service); - inline void SetupVodTrack(obs_service_t *service); - - inline void SetupStreaming(); - inline void SetupRecording(); - inline void SetupFFmpeg(); - void SetupOutputs() override; - int GetAudioBitrate(size_t i, const char *id) const; - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; - bool allowsMultiTrack(); -}; - static OBSData GetDataFromJsonFile(const char *jsonFile) { const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); @@ -1684,18 +244,6 @@ void AdvancedOutput::Update() UpdateAudioSettings(); } -static inline bool ServiceSupportsVodTrack(const char *service) -{ - static const char *vodTrackServices[] = {"Twitch"}; - - for (const char *vodTrackService : vodTrackServices) { - if (astrcmpi(vodTrackService, service) == 0) - return true; - } - - return false; -} - inline bool AdvancedOutput::allowsMultiTrack() { const char *protocol = nullptr; @@ -2366,176 +914,3 @@ bool AdvancedOutput::ReplayBufferActive() const { return obs_output_active(replayBuffer); } - -/* ------------------------------------------------------------------------ */ - -void BasicOutputHandler::SetupAutoRemux(const char *&container) -{ - bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux"); - if (autoRemux && strcmp(container, "mp4") == 0) - container = "mkv"; -} - -std::string BasicOutputHandler::GetRecordingFilename(const char *path, const char *container, bool noSpace, - bool overwrite, const char *format, bool ffmpeg) -{ - if (!ffmpeg) - SetupAutoRemux(container); - - string dst = GetOutputFilename(path, container, noSpace, overwrite, format); - lastRecordingPath = dst; - return dst; -} - -extern std::string DeserializeConfigText(const char *text); - -std::shared_future BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service, std::string audio_encoder_id, - size_t main_audio_mixer, - std::optional vod_track_mixer, - std::function)> continuation) -{ - auto start_streaming_guard = std::make_shared(); - if (!multitrackVideo) { - continuation(std::nullopt); - return start_streaming_guard->GetFuture(); - } - - multitrackVideoActive = false; - - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - bool is_custom = strncmp("rtmp_custom", obs_service_get_type(service), 11) == 0; - - std::optional custom_config = std::nullopt; - if (config_get_bool(main->Config(), "Stream1", "MultitrackVideoConfigOverrideEnabled")) - custom_config = DeserializeConfigText( - config_get_string(main->Config(), "Stream1", "MultitrackVideoConfigOverride")); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - QString key = obs_data_get_string(settings, "key"); - - const char *service_name = ""; - if (is_custom && obs_data_has_user_value(settings, "service_name")) { - service_name = obs_data_get_string(settings, "service_name"); - } else if (!is_custom) { - service_name = obs_data_get_string(settings, "service"); - } - - std::optional custom_rtmp_url; - std::optional use_rtmps; - auto server = obs_data_get_string(settings, "server"); - if (strncmp(server, "auto", 4) != 0) { - custom_rtmp_url = server; - } else { - QString server_ = server; - use_rtmps = server_.contains("rtmps", Qt::CaseInsensitive); - } - - auto service_custom_server = obs_data_get_bool(settings, "using_custom_server"); - if (custom_rtmp_url.has_value()) { - blog(LOG_INFO, "Using %sserver '%s'", service_custom_server ? "custom " : "", custom_rtmp_url->c_str()); - } - - auto maximum_aggregate_bitrate = - config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto") - ? std::nullopt - : std::make_optional( - config_get_int(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrate")); - - auto maximum_video_tracks = config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracksAuto") - ? std::nullopt - : std::make_optional(config_get_int( - main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracks")); - - auto stream_dump_config = GenerateMultitrackVideoStreamDumpConfig(); - - auto continue_on_main_thread = [&, start_streaming_guard, service = OBSService{service}, - continuation = - std::move(continuation)](std::optional error) { - if (error) { - OBSDataAutoRelease service_settings = obs_service_get_settings(service); - auto multitrack_video_name = QTStr("Basic.Settings.Stream.MultitrackVideoLabel"); - if (obs_data_has_user_value(service_settings, "multitrack_video_name")) { - multitrack_video_name = obs_data_get_string(service_settings, "multitrack_video_name"); - } - - multitrackVideoActive = false; - if (!error->ShowDialog(main, multitrack_video_name)) - return continuation(false); - return continuation(std::nullopt); - } - - multitrackVideoActive = true; - - auto signal_handler = multitrackVideo->StreamingSignalHandler(); - - streamDelayStarting.Connect(signal_handler, "starting", OBSStreamStarting, this); - streamStopping.Connect(signal_handler, "stopping", OBSStreamStopping, this); - - startStreaming.Connect(signal_handler, "start", OBSStartStreaming, this); - stopStreaming.Connect(signal_handler, "stop", OBSStopStreaming, this); - return continuation(true); - }; - - QThreadPool::globalInstance()->start([=, multitrackVideo = multitrackVideo.get(), - service_name = std::string{service_name}, service = OBSService{service}, - stream_dump_config = OBSData{stream_dump_config}, - start_streaming_guard = start_streaming_guard]() mutable { - std::optional error; - try { - multitrackVideo->PrepareStreaming(main, service_name.c_str(), service, custom_rtmp_url, key, - audio_encoder_id.c_str(), maximum_aggregate_bitrate, - maximum_video_tracks, custom_config, stream_dump_config, - main_audio_mixer, vod_track_mixer, use_rtmps); - } catch (const MultitrackVideoError &error_) { - error.emplace(error_); - } - - QMetaObject::invokeMethod(main, [=] { continue_on_main_thread(error); }); - }); - - return start_streaming_guard->GetFuture(); -} - -OBSDataAutoRelease BasicOutputHandler::GenerateMultitrackVideoStreamDumpConfig() -{ - auto stream_dump_enabled = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpEnabled"); - - if (!stream_dump_enabled) - return nullptr; - - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - bool useMP4 = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpAsMP4"); - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, useMP4 ? "mp4" : "flv", noSpace, overwriteIfExists, f.c_str(), - // never remux stream dump - false); - obs_data_set_string(settings, "path", strPath.c_str()); - - if (useMP4) { - obs_data_set_bool(settings, "use_mp4", true); - obs_data_set_string(settings, "muxer_settings", "write_encoder_info=1"); - } - - return settings; -} - -BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main) -{ - return new SimpleOutput(main); -} - -BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main) -{ - return new AdvancedOutput(main); -} diff --git a/frontend/utility/AdvancedOutput.hpp b/frontend/utility/AdvancedOutput.hpp index 30d203174..a06191420 100644 --- a/frontend/utility/AdvancedOutput.hpp +++ b/frontend/utility/AdvancedOutput.hpp @@ -1,1408 +1,6 @@ -#include -#include -#include -#include -#include -#include -#include "audio-encoders.hpp" -#include "multitrack-video-error.hpp" -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" -#include "window-basic-vcam.hpp" +#pragma once -using namespace std; - -extern bool EncoderAvailable(const char *encoder); - -volatile bool streaming_active = false; -volatile bool recording_active = false; -volatile bool recording_paused = false; -volatile bool replaybuf_active = false; -volatile bool virtualcam_active = false; - -#define RTMP_PROTOCOL "rtmp" -#define SRT_PROTOCOL "srt" -#define RIST_PROTOCOL "rist" - -static void OBSStreamStarting(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - return; - - output->delayActive = true; - QMetaObject::invokeMethod(output->main, "StreamDelayStarting", Q_ARG(int, sec)); -} - -static void OBSStreamStopping(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - QMetaObject::invokeMethod(output->main, "StreamStopping"); - else - QMetaObject::invokeMethod(output->main, "StreamDelayStopping", Q_ARG(int, sec)); -} - -static void OBSStartStreaming(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->streamingActive = true; - os_atomic_set_bool(&streaming_active, true); - QMetaObject::invokeMethod(output->main, "StreamingStart"); -} - -static void OBSStopStreaming(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->streamingActive = false; - output->delayActive = false; - output->multitrackVideoActive = false; - os_atomic_set_bool(&streaming_active, false); - QMetaObject::invokeMethod(output->main, "StreamingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSStartRecording(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->recordingActive = true; - os_atomic_set_bool(&recording_active, true); - QMetaObject::invokeMethod(output->main, "RecordingStart"); -} - -static void OBSStopRecording(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->recordingActive = false; - os_atomic_set_bool(&recording_active, false); - os_atomic_set_bool(&recording_paused, false); - QMetaObject::invokeMethod(output->main, "RecordingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSRecordStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "RecordStopping"); -} - -static void OBSRecordFileChanged(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - const char *next_file = calldata_string(params, "next_file"); - - QString arg_last_file = QString::fromUtf8(output->lastRecordingPath.c_str()); - - QMetaObject::invokeMethod(output->main, "RecordingFileChanged", Q_ARG(QString, arg_last_file)); - - output->lastRecordingPath = next_file; -} - -static void OBSStartReplayBuffer(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->replayBufferActive = true; - os_atomic_set_bool(&replaybuf_active, true); - QMetaObject::invokeMethod(output->main, "ReplayBufferStart"); -} - -static void OBSStopReplayBuffer(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->replayBufferActive = false; - os_atomic_set_bool(&replaybuf_active, false); - QMetaObject::invokeMethod(output->main, "ReplayBufferStop", Q_ARG(int, code)); -} - -static void OBSReplayBufferStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferStopping"); -} - -static void OBSReplayBufferSaved(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferSaved", Qt::QueuedConnection); -} - -static void OBSStartVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->virtualCamActive = true; - os_atomic_set_bool(&virtualcam_active, true); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStart"); -} - -static void OBSStopVirtualCam(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->virtualCamActive = false; - os_atomic_set_bool(&virtualcam_active, false); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", Q_ARG(int, code)); -} - -static void OBSDeactivateVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->DestroyVirtualCamView(); -} - -/* ------------------------------------------------------------------------ */ - -struct StartMultitrackVideoStreamingGuard { - StartMultitrackVideoStreamingGuard() { future = guard.get_future().share(); }; - ~StartMultitrackVideoStreamingGuard() { guard.set_value(); } - - std::shared_future GetFuture() const { return future; } - - static std::shared_future MakeReadyFuture() - { - StartMultitrackVideoStreamingGuard guard; - return guard.GetFuture(); - } - -private: - std::promise guard; - std::shared_future future; -}; - -/* ------------------------------------------------------------------------ */ - -static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleAACEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static bool CreateSimpleOpusEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleOpusEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, - const char *prot_test2 = nullptr) -{ - return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && - (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; -} - -static bool return_first_id(void *data, const char *id) -{ - const char **output = (const char **)data; - - *output = id; - return false; -} - -static const char *GetStreamOutputType(const obs_service_t *service) -{ - const char *protocol = obs_service_get_protocol(service); - const char *output = nullptr; - - if (!protocol) { - blog(LOG_WARNING, "The service '%s' has no protocol set", obs_service_get_id(service)); - return nullptr; - } - - if (!obs_is_output_protocol_registered(protocol)) { - blog(LOG_WARNING, "The protocol '%s' is not registered", protocol); - return nullptr; - } - - /* Check if the service has a preferred output type */ - output = obs_service_get_preferred_output_type(service); - if (output) { - if ((obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0) - return output; - - blog(LOG_WARNING, "The output '%s' is not registered, fallback to another one", output); - } - - /* Otherwise, prefer first-party output types */ - if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) { - return "rtmp_output"; - } else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) { - return "ffmpeg_hls_muxer"; - } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", "RIST")) { - return "ffmpeg_mpegts_muxer"; - } - - /* If third-party protocol, use the first enumerated type */ - obs_enum_output_types_with_protocol(protocol, &output, return_first_id); - if (output) - return output; - - blog(LOG_WARNING, "No output compatible with the service '%s' is registered", obs_service_get_id(service)); - - return nullptr; -} - -/* ------------------------------------------------------------------------ */ - -inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) -{ - if (main->vcamEnabled) { - virtualCam = obs_output_create(VIRTUAL_CAM_ID, "virtualcam_output", nullptr, nullptr); - - signal_handler_t *signal = obs_output_get_signal_handler(virtualCam); - startVirtualCam.Connect(signal, "start", OBSStartVirtualCam, this); - stopVirtualCam.Connect(signal, "stop", OBSStopVirtualCam, this); - deactivateVirtualCam.Connect(signal, "deactivate", OBSDeactivateVirtualCam, this); - } - - auto multitrack_enabled = config_get_bool(main->Config(), "Stream1", "EnableMultitrackVideo"); - if (!config_has_user_value(main->Config(), "Stream1", "EnableMultitrackVideo")) { - auto service = main_->GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - multitrack_enabled = obs_data_has_user_value(settings, "multitrack_video_configuration_url"); - } - if (multitrack_enabled) - multitrackVideo = make_unique(); -} - -extern void log_vcam_changed(const VCamConfig &config, bool starting); - -bool BasicOutputHandler::StartVirtualCam() -{ - if (!main->vcamEnabled) - return false; - - bool typeIsProgram = main->vcamConfig.type == VCamOutputType::ProgramView; - - if (!virtualCamView && !typeIsProgram) - virtualCamView = obs_view_create(); - - UpdateVirtualCamOutputSource(); - - if (!virtualCamVideo) { - virtualCamVideo = typeIsProgram ? obs_get_video() : obs_view_add(virtualCamView); - - if (!virtualCamVideo) - return false; - } - - obs_output_set_media(virtualCam, virtualCamVideo, obs_get_audio()); - if (!Active()) - SetupOutputs(); - - bool success = obs_output_start(virtualCam); - if (!success) { - QString errorReason; - - const char *error = obs_output_get_last_error(virtualCam); - if (error) { - errorReason = QT_UTF8(error); - } else { - errorReason = QTStr("Output.StartFailedGeneric"); - } - - QMessageBox::critical(main, QTStr("Output.StartVirtualCamFailed"), errorReason); - - DestroyVirtualCamView(); - } - - log_vcam_changed(main->vcamConfig, true); - - return success; -} - -void BasicOutputHandler::StopVirtualCam() -{ - if (main->vcamEnabled) { - obs_output_stop(virtualCam); - } -} - -bool BasicOutputHandler::VirtualCamActive() const -{ - if (main->vcamEnabled) { - return obs_output_active(virtualCam); - } - return false; -} - -void BasicOutputHandler::UpdateVirtualCamOutputSource() -{ - if (!main->vcamEnabled || !virtualCamView) - return; - - OBSSourceAutoRelease source; - - switch (main->vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - DestroyVirtualCameraScene(); - return; - case VCamOutputType::PreviewOutput: { - DestroyVirtualCameraScene(); - OBSSource s = main->GetCurrentSceneSource(); - obs_source_get_ref(s); - source = s.Get(); - break; - } - case VCamOutputType::SceneOutput: - DestroyVirtualCameraScene(); - source = obs_get_source_by_name(main->vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - OBSSourceAutoRelease s = obs_get_source_by_name(main->vcamConfig.source.c_str()); - - if (!vCamSourceScene) - vCamSourceScene = obs_scene_create_private("vcam_source"); - source = obs_source_get_ref(obs_scene_get_source(vCamSourceScene)); - - if (vCamSourceSceneItem && (obs_sceneitem_get_source(vCamSourceSceneItem) != s)) { - obs_sceneitem_remove(vCamSourceSceneItem); - vCamSourceSceneItem = nullptr; - } - - if (!vCamSourceSceneItem) { - vCamSourceSceneItem = obs_scene_add(vCamSourceScene, s); - - obs_sceneitem_set_bounds_type(vCamSourceSceneItem, OBS_BOUNDS_SCALE_INNER); - obs_sceneitem_set_bounds_alignment(vCamSourceSceneItem, OBS_ALIGN_CENTER); - - const struct vec2 size = { - (float)obs_source_get_width(source), - (float)obs_source_get_height(source), - }; - obs_sceneitem_set_bounds(vCamSourceSceneItem, &size); - } - break; - } - - OBSSourceAutoRelease current = obs_view_get_source(virtualCamView, 0); - if (source != current) - obs_view_set_source(virtualCamView, 0, source); -} - -void BasicOutputHandler::DestroyVirtualCamView() -{ - if (main->vcamConfig.type == VCamOutputType::ProgramView) { - virtualCamVideo = nullptr; - return; - } - - obs_view_remove(virtualCamView); - obs_view_set_source(virtualCamView, 0, nullptr); - virtualCamVideo = nullptr; - - obs_view_destroy(virtualCamView); - virtualCamView = nullptr; - - DestroyVirtualCameraScene(); -} - -void BasicOutputHandler::DestroyVirtualCameraScene() -{ - if (!vCamSourceScene) - return; - - obs_scene_release(vCamSourceScene); - vCamSourceScene = nullptr; - vCamSourceSceneItem = nullptr; -} - -/* ------------------------------------------------------------------------ */ - -struct SimpleOutput : BasicOutputHandler { - OBSEncoder audioStreaming; - OBSEncoder videoStreaming; - OBSEncoder audioRecording; - OBSEncoder audioArchive; - OBSEncoder videoRecording; - OBSEncoder audioTrack[MAX_AUDIO_MIXES]; - - string videoEncoder; - string videoQuality; - bool usingRecordingPreset = false; - bool recordingConfigured = false; - bool ffmpegOutput = false; - bool lowCPUx264 = false; - - SimpleOutput(OBSBasic *main_); - - int CalcCRF(int crf); - - void UpdateRecordingSettings_x264_crf(int crf); - void UpdateRecordingSettings_qsv11(int crf, bool av1); - void UpdateRecordingSettings_nvenc(int cqp); - void UpdateRecordingSettings_nvenc_hevc_av1(int cqp); - void UpdateRecordingSettings_amd_cqp(int cqp); - void UpdateRecordingSettings_apple(int quality); -#ifdef ENABLE_HEVC - void UpdateRecordingSettings_apple_hevc(int quality); -#endif - void UpdateRecordingSettings(); - void UpdateRecordingAudioSettings(); - virtual void Update() override; - - void SetupOutputs() override; - int GetAudioBitrate() const; - - void LoadRecordingPreset_Lossy(const char *encoder); - void LoadRecordingPreset_Lossless(); - void LoadRecordingPreset(); - - void LoadStreamingPreset_Lossy(const char *encoder); - - void UpdateRecording(); - bool ConfigureRecording(bool useReplayBuffer); - - bool IsVodTrackEnabled(obs_service_t *service); - void SetupVodTrack(obs_service_t *service); - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; -}; - -void SimpleOutput::LoadRecordingPreset_Lossless() -{ - fileOutput = obs_output_create("ffmpeg_output", "simple_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(simple output)"; - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "format_name", "avi"); - obs_data_set_string(settings, "video_encoder", "utvideo"); - obs_data_set_string(settings, "audio_encoder", "pcm_s16le"); - - obs_output_update(fileOutput, settings); -} - -void SimpleOutput::LoadRecordingPreset_Lossy(const char *encoderId) -{ - videoRecording = obs_video_encoder_create(encoderId, "simple_video_recording", nullptr, nullptr); - if (!videoRecording) - throw "Failed to create video recording encoder (simple output)"; - obs_encoder_release(videoRecording); -} - -void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId) -{ - videoStreaming = obs_video_encoder_create(encoderId, "simple_video_stream", nullptr, nullptr); - if (!videoStreaming) - throw "Failed to create video streaming encoder (simple output)"; - obs_encoder_release(videoStreaming); -} - -/* mistakes have been made to lead us to this. */ -const char *get_simple_output_encoder(const char *encoder) -{ - if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - return "obs_qsv11_v2"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - return "obs_qsv11_av1"; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - return "h264_texture_amf"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - return "h265_texture_amf"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - return "av1_texture_amf"; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - return EncoderAvailable("obs_nvenc_h264_tex") ? "obs_nvenc_h264_tex" : "ffmpeg_nvenc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - return EncoderAvailable("obs_nvenc_hevc_tex") ? "obs_nvenc_hevc_tex" : "ffmpeg_hevc_nvenc"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - return "obs_nvenc_av1_tex"; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.avc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.hevc"; -#endif - } - - return "obs_x264"; -} - -void SimpleOutput::LoadRecordingPreset() -{ - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "RecEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "RecAudioEncoder"); - - videoEncoder = encoder; - videoQuality = quality; - ffmpegOutput = false; - - if (strcmp(quality, "Stream") == 0) { - videoRecording = videoStreaming; - audioRecording = audioStreaming; - usingRecordingPreset = false; - return; - - } else if (strcmp(quality, "Lossless") == 0) { - LoadRecordingPreset_Lossless(); - usingRecordingPreset = true; - ffmpegOutput = true; - return; - - } else { - lowCPUx264 = false; - - if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) - lowCPUx264 = true; - LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder)); - usingRecordingPreset = true; - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioRecording, 192, "simple_opus_recording", 0); - else - success = CreateSimpleAACEncoder(audioRecording, 192, "simple_aac_recording", 0); - - if (!success) - throw "Failed to create audio recording encoder " - "(simple output)"; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[23]; - if (strcmp(audio_encoder, "opus") == 0) { - snprintf(name, sizeof name, "simple_opus_recording%d", i); - success = CreateSimpleOpusEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } else { - snprintf(name, sizeof name, "simple_aac_recording%d", i); - success = CreateSimpleAACEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } - if (!success) - throw "Failed to create multi-track audio recording encoder " - "(simple output)"; - } - } -} - -#define SIMPLE_ARCHIVE_NAME "simple_archive_audio" - -SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - - LoadStreamingPreset_Lossy(get_simple_output_encoder(encoder)); - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioStreaming, GetAudioBitrate(), "simple_opus", 0); - else - success = CreateSimpleAACEncoder(audioStreaming, GetAudioBitrate(), "simple_aac", 0); - - if (!success) - throw "Failed to create audio streaming encoder (simple output)"; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - else - success = CreateSimpleAACEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - - if (!success) - throw "Failed to create audio archive encoder (simple output)"; - - LoadRecordingPreset(); - - if (!ffmpegOutput) { - bool useReplayBuffer = config_get_bool(main->Config(), "SimpleOutput", "RecRB"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool use_native = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(use_native ? "mp4_output" : "ffmpeg_muxer", "simple_file_output", - nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(simple output)"; - } - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); -} - -int SimpleOutput::GetAudioBitrate() const -{ - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); - - if (strcmp(audio_encoder, "opus") == 0) - return FindClosestAvailableSimpleOpusBitrate(bitrate); - - return FindClosestAvailableSimpleAACBitrate(bitrate); -} - -void SimpleOutput::Update() -{ - OBSDataAutoRelease videoSettings = obs_data_create(); - OBSDataAutoRelease audioSettings = obs_data_create(); - - int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); - int audioBitrate = GetAudioBitrate(); - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - const char *custom = config_get_string(main->Config(), "SimpleOutput", "x264Settings"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *encoder_id = obs_encoder_get_id(videoStreaming); - const char *presetType; - const char *preset; - - if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - presetType = "AMDPreset"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset2"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - presetType = "NVENCPreset2"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - presetType = "AMDAV1Preset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - presetType = "NVENCPreset2"; - - } else { - presetType = "Preset"; - } - - preset = config_get_string(main->Config(), "SimpleOutput", presetType); - - /* Only use preset2 for legacy/FFmpeg NVENC Encoder. */ - if (strncmp(encoder_id, "ffmpeg_", 7) == 0 && strcmp(presetType, "NVENCPreset2") == 0) { - obs_data_set_string(videoSettings, "preset2", preset); - } else { - obs_data_set_string(videoSettings, "preset", preset); - } - - obs_data_set_string(videoSettings, "rate_control", "CBR"); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - - if (advanced) - obs_data_set_string(videoSettings, "x264opts", custom); - - obs_data_set_string(audioSettings, "rate_control", "CBR"); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - - obs_service_apply_encoder_settings(main->GetService(), videoSettings, audioSettings); - - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - } - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, videoSettings); - obs_encoder_update(audioStreaming, audioSettings); - obs_encoder_update(audioArchive, audioSettings); -} - -void SimpleOutput::UpdateRecordingAudioSettings() -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", 192); - obs_data_set_string(settings, "rate_control", "CBR"); - - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv || strcmp(quality, "Stream") == 0) { - obs_encoder_update(audioRecording, settings); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_update(audioTrack[i], settings); - } - } - } -} - -#define CROSS_DIST_CUTOFF 2000.0 - -int SimpleOutput::CalcCRF(int crf) -{ - int cx = config_get_uint(main->Config(), "Video", "OutputCX"); - int cy = config_get_uint(main->Config(), "Video", "OutputCY"); - double fCX = double(cx); - double fCY = double(cy); - - if (lowCPUx264) - crf -= 2; - - double crossDist = sqrt(fCX * fCX + fCY * fCY); - double crfResReduction = fmin(CROSS_DIST_CUTOFF, crossDist) / CROSS_DIST_CUTOFF; - crfResReduction = (1.0 - crfResReduction) * 10.0; - - return crf - int(crfResReduction); -} - -void SimpleOutput::UpdateRecordingSettings_x264_crf(int crf) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "crf", crf); - obs_data_set_bool(settings, "use_bufsize", true); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", lowCPUx264 ? "ultrafast" : "veryfast"); - - obs_encoder_update(videoRecording, settings); -} - -static bool icq_available(obs_encoder_t *encoder) -{ - obs_properties_t *props = obs_encoder_properties(encoder); - obs_property_t *p = obs_properties_get(props, "rate_control"); - bool icq_found = false; - - size_t num = obs_property_list_item_count(p); - for (size_t i = 0; i < num; i++) { - const char *val = obs_property_list_item_string(p, i); - if (strcmp(val, "ICQ") == 0) { - icq_found = true; - break; - } - } - - obs_properties_destroy(props); - return icq_found; -} - -void SimpleOutput::UpdateRecordingSettings_qsv11(int crf, bool av1) -{ - bool icq = icq_available(videoRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "profile", "high"); - - if (icq && !av1) { - obs_data_set_string(settings, "rate_control", "ICQ"); - obs_data_set_int(settings, "icq_quality", crf); - } else { - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_int(settings, "cqp", crf); - } - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc_hevc_av1(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_apple(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} - -#ifdef ENABLE_HEVC -void SimpleOutput::UpdateRecordingSettings_apple_hevc(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} -#endif - -void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", "quality"); - obs_data_set_int(settings, "cqp", cqp); - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings() -{ - bool ultra_hq = (videoQuality == "HQ"); - int crf = CalcCRF(ultra_hq ? 16 : 23); - - if (astrcmp_n(videoEncoder.c_str(), "x264", 4) == 0) { - UpdateRecordingSettings_x264_crf(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV) { - UpdateRecordingSettings_qsv11(crf, false); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV_AV1) { - UpdateRecordingSettings_qsv11(crf, true); - - } else if (videoEncoder == SIMPLE_ENCODER_AMD) { - UpdateRecordingSettings_amd_cqp(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_AMD_HEVC) { - UpdateRecordingSettings_amd_cqp(crf); -#endif - - } else if (videoEncoder == SIMPLE_ENCODER_AMD_AV1) { - UpdateRecordingSettings_amd_cqp(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_NVENC) { - UpdateRecordingSettings_nvenc(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_HEVC) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); -#endif - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_AV1) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_H264) { - /* These are magic numbers. 0 - 100, more is better. */ - UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_HEVC) { - UpdateRecordingSettings_apple_hevc(ultra_hq ? 70 : 50); -#endif - } - UpdateRecordingAudioSettings(); -} - -inline void SimpleOutput::SetupOutputs() -{ - SimpleOutput::Update(); - obs_encoder_set_video(videoStreaming, obs_get_video()); - obs_encoder_set_audio(audioStreaming, obs_get_audio()); - obs_encoder_set_audio(audioArchive, obs_get_audio()); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (usingRecordingPreset) { - if (ffmpegOutput) { - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - } else { - obs_encoder_set_video(videoRecording, obs_get_video()); - if (flv) { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_set_audio(audioTrack[i], obs_get_audio()); - } - } - } - } - } else { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } -} - -const char *FindAudioEncoderFromCodec(const char *type) -{ - const char *alt_enc_id = nullptr; - size_t i = 0; - - while (obs_enum_encoder_types(i++, &alt_enc_id)) { - const char *codec = obs_get_encoder_codec(alt_enc_id); - if (strcmp(type, codec) == 0) { - return alt_enc_id; - } - } - - return nullptr; -} - -std::shared_future SimpleOutput::SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation) -{ - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - auto audio_bitrate = GetAudioBitrate(); - auto vod_track_mixer = IsVodTrackEnabled(service) ? std::optional{1} : std::nullopt; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "simple_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, audioStreaming, 0); - obs_output_set_service(streamOutput, service); - return true; - }; - - return SetupMultitrackVideo(service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0, vod_track_mixer, - [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -static inline bool ServiceSupportsVodTrack(const char *service); - -static void clear_archive_encoder(obs_output_t *output, const char *expected_name) -{ - obs_encoder_t *last = obs_output_get_audio_encoder(output, 1); - bool clear = false; - - /* ensures that we don't remove twitch's soundtrack encoder */ - if (last) { - const char *name = obs_encoder_get_name(last); - clear = name && strcmp(name, expected_name) == 0; - obs_encoder_release(last); - } - - if (clear) - obs_output_set_audio_encoder(output, nullptr, 1); -} - -bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service) -{ - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enable = config_get_bool(main->Config(), "SimpleOutput", "VodTrackEnabled"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *name = obs_data_get_string(settings, "service"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) - return enableForCustomServer ? enable : false; - else - return advanced && enable && ServiceSupportsVodTrack(name); -} - -void SimpleOutput::SetupVodTrack(obs_service_t *service) -{ - if (IsVodTrackEnabled(service)) - obs_output_set_audio_encoder(streamOutput, audioArchive, 1); - else - clear_archive_encoder(streamOutput, SIMPLE_ARCHIVE_NAME); -} - -bool SimpleOutput::StartStreaming(obs_service_t *service) -{ - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_uint(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_uint(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - - if (!multitrackVideo || !multitrackVideoActive) - SetupVodTrack(service); - - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -void SimpleOutput::UpdateRecording() -{ - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - int idx = 0; - int idx2 = 0; - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - - if (replayBufferActive || recordingActive) - return; - - if (usingRecordingPreset) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(streamOutput)) { - Update(); - } - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput) { - obs_output_set_video_encoder(fileOutput, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(fileOutput, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, audioTrack[i], idx++); - } - } - } - } - if (replayBuffer) { - obs_output_set_video_encoder(replayBuffer, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(replayBuffer, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(replayBuffer, audioTrack[i], idx2++); - } - } - } - } - - recordingConfigured = true; -} - -bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) -{ - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - const char *format = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *mux = config_get_string(main->Config(), "SimpleOutput", "MuxerCustom"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - int rbTime = config_get_int(main->Config(), "SimpleOutput", "RecRBTime"); - int rbSize = config_get_int(main->Config(), "SimpleOutput", "RecRBSize"); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - - bool is_fragmented = strncmp(format, "fragmented", 10) == 0; - bool is_lossless = videoQuality == "Lossless"; - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - if (updateReplayBuffer) { - f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(format); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usingRecordingPreset ? rbSize : 0); - } else { - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, ffmpegOutput ? "avi" : format, noSpace, overwriteIfExists, - f.c_str(), ffmpegOutput); - obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str()); - if (ffmpegOutput) - obs_output_set_mixers(fileOutput, tracks); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && !is_lossless && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented && !is_lossless) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - if (updateReplayBuffer) - obs_output_update(replayBuffer, settings); - else - obs_output_update(fileOutput, settings); - - return true; -} - -bool SimpleOutput::StartRecording() -{ - UpdateRecording(); - if (!ConfigureRecording(false)) - return false; - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool SimpleOutput::StartReplayBuffer() -{ - UpdateRecording(); - if (!ConfigureRecording(true)) - return false; - if (!obs_output_start(replayBuffer)) { - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), QTStr("Output.StartFailedGeneric")); - return false; - } - - return true; -} - -void SimpleOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void SimpleOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void SimpleOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool SimpleOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool SimpleOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool SimpleOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ +#include "BasicOutputHandler.hpp" struct AdvancedOutput : BasicOutputHandler { OBSEncoder streamAudioEnc; @@ -1447,1095 +45,3 @@ struct AdvancedOutput : BasicOutputHandler { virtual bool ReplayBufferActive() const override; bool allowsMultiTrack(); }; - -static OBSData GetDataFromJsonFile(const char *jsonFile) -{ - const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); - - const OBSProfile ¤tProfile = basic->GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(jsonFile); - - OBSDataAutoRelease data = nullptr; - - if (!jsonFilePath.empty()) { - BPtr jsonData = os_quick_read_utf8_file(jsonFilePath.u8string().c_str()); - - if (!!jsonData) { - data = obs_data_create_from_json(jsonData); - } - } - - if (!data) { - data = obs_data_create(); - } - - return data.Get(); -} - -static void ApplyEncoderDefaults(OBSData &settings, const obs_encoder_t *encoder) -{ - OBSData dataRet = obs_encoder_get_defaults(encoder); - obs_data_release(dataRet); - - if (!!settings) - obs_data_apply(dataRet, settings); - settings = std::move(dataRet); -} - -#define ADV_ARCHIVE_NAME "adv_archive_audio" - -#ifdef __APPLE__ -static void translate_macvth264_encoder(const char *&encoder) -{ - if (strcmp(encoder, "vt_h264_hw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264.gva"; - } else if (strcmp(encoder, "vt_h264_sw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264"; - } -} -#endif - -AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *recType = config_get_string(main->Config(), "AdvOut", "RecType"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - const char *streamAudioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recordEncoder = config_get_string(main->Config(), "AdvOut", "RecEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); -#ifdef __APPLE__ - translate_macvth264_encoder(streamEncoder); - translate_macvth264_encoder(recordEncoder); -#endif - - ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - ffmpegRecording = ffmpegOutput && config_get_bool(main->Config(), "AdvOut", "FFOutputToFile"); - useStreamEncoder = astrcmpi(recordEncoder, "none") == 0; - useStreamAudioEncoder = astrcmpi(recAudioEncoder, "none") == 0; - - OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); - OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); - - if (ffmpegOutput) { - fileOutput = obs_output_create("ffmpeg_output", "adv_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(advanced output)"; - } else { - bool useReplayBuffer = config_get_bool(main->Config(), "AdvOut", "RecRB"); - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool native_muxer = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(native_muxer ? "mp4_output" : "ffmpeg_muxer", "adv_file_output", nullptr, - nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(advanced output)"; - - if (!useStreamEncoder) { - videoRecording = obs_video_encoder_create(recordEncoder, "advanced_video_recording", - recordEncSettings, nullptr); - if (!videoRecording) - throw "Failed to create recording video " - "encoder (advanced output)"; - obs_encoder_release(videoRecording); - } - } - - videoStreaming = obs_video_encoder_create(streamEncoder, "advanced_video_stream", streamEncSettings, nullptr); - if (!videoStreaming) - throw "Failed to create streaming video encoder " - "(advanced output)"; - obs_encoder_release(videoStreaming); - - const char *rate_control = - obs_data_get_string(useStreamEncoder ? streamEncSettings : recordEncSettings, "rate_control"); - if (!rate_control) - rate_control = ""; - usesBitrate = astrcmpi(rate_control, "CBR") == 0 || astrcmpi(rate_control, "VBR") == 0 || - astrcmpi(rate_control, "ABR") == 0; - - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[19]; - snprintf(name, sizeof(name), "adv_record_audio_%d", i); - - recordTrack[i] = obs_audio_encoder_create(useStreamAudioEncoder ? streamAudioEncoder : recAudioEncoder, - name, nullptr, i, nullptr); - - if (!recordTrack[i]) { - throw "Failed to create audio encoder " - "(advanced output)"; - } - - obs_encoder_release(recordTrack[i]); - - snprintf(name, sizeof(name), "adv_stream_audio_%d", i); - streamTrack[i] = obs_audio_encoder_create(streamAudioEncoder, name, nullptr, i, nullptr); - - if (!streamTrack[i]) { - throw "Failed to create streaming audio encoders " - "(advanced output)"; - } - - obs_encoder_release(streamTrack[i]); - } - - std::string id; - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - streamAudioEnc = - obs_audio_encoder_create(streamAudioEncoder, "adv_stream_audio", nullptr, streamTrackIndex, nullptr); - if (!streamAudioEnc) - throw "Failed to create streaming audio encoder " - "(advanced output)"; - obs_encoder_release(streamAudioEnc); - - id = ""; - int vodTrack = config_get_int(main->Config(), "AdvOut", "VodTrackIndex") - 1; - streamArchiveEnc = obs_audio_encoder_create(streamAudioEncoder, ADV_ARCHIVE_NAME, nullptr, vodTrack, nullptr); - if (!streamArchiveEnc) - throw "Failed to create archive audio encoder " - "(advanced output)"; - obs_encoder_release(streamArchiveEnc); - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); - recordFileChanged.Connect(obs_output_get_signal_handler(fileOutput), "file_changed", OBSRecordFileChanged, - this); -} - -void AdvancedOutput::UpdateStreamSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - bool dynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - - OBSData settings = GetDataFromJsonFile("streamEncoder.json"); - ApplyEncoderDefaults(settings, videoStreaming); - - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings, "bitrate"); - int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(settings, "bitrate", bitrate); - } - - int enforced_keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - if (keyint_sec != 0 && keyint_sec < enforced_keyint_sec) - obs_data_set_int(settings, "keyint_sec", keyint_sec); - } else { - blog(LOG_WARNING, "User is ignoring service settings."); - } - - if (dynBitrate && strstr(streamEncoder, "nvenc") != nullptr) - obs_data_set_bool(settings, "lookahead", false); - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, settings); -} - -inline void AdvancedOutput::UpdateRecordingSettings() -{ - OBSData settings = GetDataFromJsonFile("recordEncoder.json"); - obs_encoder_update(videoRecording, settings); -} - -void AdvancedOutput::Update() -{ - UpdateStreamSettings(); - if (!useStreamEncoder && !ffmpegOutput) - UpdateRecordingSettings(); - UpdateAudioSettings(); -} - -static inline bool ServiceSupportsVodTrack(const char *service) -{ - static const char *vodTrackServices[] = {"Twitch"}; - - for (const char *vodTrackService : vodTrackServices) { - if (astrcmpi(vodTrackService, service) == 0) - return true; - } - - return false; -} - -inline bool AdvancedOutput::allowsMultiTrack() -{ - const char *protocol = nullptr; - obs_service_t *service_obj = main->GetService(); - protocol = obs_service_get_protocol(service_obj); - if (!protocol) - return false; - return astrcmpi_n(protocol, SRT_PROTOCOL, strlen(SRT_PROTOCOL)) == 0 || - astrcmpi_n(protocol, RIST_PROTOCOL, strlen(RIST_PROTOCOL)) == 0; -} - -inline void AdvancedOutput::SetupStreaming() -{ - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RescaleFilter"); - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - bool is_multitrack_output = allowsMultiTrack(); - - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - obs_encoder_set_scaled_size(videoStreaming, cx, cy); - obs_encoder_set_gpu_scale_type(videoStreaming, (obs_scale_type)rescaleFilter); - - const char *id = obs_service_get_id(main->GetService()); - if (strcmp(id, "rtmp_custom") == 0) { - OBSDataAutoRelease settings = obs_data_create(); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - obs_encoder_update(videoStreaming, settings); - } -} - -inline void AdvancedOutput::SetupRecording() -{ - const char *path = config_get_string(main->Config(), "AdvOut", "RecFilePath"); - const char *mux = config_get_string(main->Config(), "AdvOut", "RecMuxerCustom"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RecRescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RecRescaleFilter"); - int tracks; - - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); - - bool is_fragmented = strncmp(recFormat, "fragmented", 10) == 0; - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv) - tracks = config_get_int(main->Config(), "AdvOut", "FLVTrack"); - else - tracks = config_get_int(main->Config(), "AdvOut", "RecTracks"); - - OBSDataAutoRelease settings = obs_data_create(); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - - /* Hack to allow recordings without any audio tracks selected. It is no - * longer possible to select such a configuration in settings, but legacy - * configurations might still have this configured and we don't want to - * just break them. */ - if (tracks == 0) - tracks = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - - if (useStreamEncoder) { - obs_output_set_video_encoder(fileOutput, videoStreaming); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoStreaming); - } else { - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - obs_encoder_set_scaled_size(videoRecording, cx, cy); - obs_encoder_set_gpu_scale_type(videoRecording, (obs_scale_type)rescaleFilter); - obs_output_set_video_encoder(fileOutput, videoRecording); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoRecording); - } - - if (!flv) { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[i], idx); - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[i], idx); - idx++; - } - } - } else if (flv && tracks != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[tracks - 1], idx); - - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[tracks - 1], idx); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - obs_data_set_string(settings, "path", path); - obs_output_update(fileOutput, settings); - if (replayBuffer) - obs_output_update(replayBuffer, settings); -} - -inline void AdvancedOutput::SetupFFmpeg() -{ - const char *url = config_get_string(main->Config(), "AdvOut", "FFURL"); - int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate"); - int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize"); - bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "FFRescaleRes"); - const char *formatName = config_get_string(main->Config(), "AdvOut", "FFFormat"); - const char *mimeType = config_get_string(main->Config(), "AdvOut", "FFFormatMimeType"); - const char *muxCustom = config_get_string(main->Config(), "AdvOut", "FFMCustom"); - const char *vEncoder = config_get_string(main->Config(), "AdvOut", "FFVEncoder"); - int vEncoderId = config_get_int(main->Config(), "AdvOut", "FFVEncoderId"); - const char *vEncCustom = config_get_string(main->Config(), "AdvOut", "FFVCustom"); - int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate"); - int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes"); - const char *aEncoder = config_get_string(main->Config(), "AdvOut", "FFAEncoder"); - int aEncoderId = config_get_int(main->Config(), "AdvOut", "FFAEncoderId"); - const char *aEncCustom = config_get_string(main->Config(), "AdvOut", "FFACustom"); - - OBSDataArrayAutoRelease audio_names = obs_data_array_create(); - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - - const char *audioName = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - OBSDataAutoRelease item = obs_data_create(); - obs_data_set_string(item, "name", audioName); - obs_data_array_push_back(audio_names, item); - } - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_array(settings, "audio_names", audio_names); - obs_data_set_string(settings, "url", url); - obs_data_set_string(settings, "format_name", formatName); - obs_data_set_string(settings, "format_mime_type", mimeType); - obs_data_set_string(settings, "muxer_settings", muxCustom); - obs_data_set_int(settings, "gop_size", gopSize); - obs_data_set_int(settings, "video_bitrate", vBitrate); - obs_data_set_string(settings, "video_encoder", vEncoder); - obs_data_set_int(settings, "video_encoder_id", vEncoderId); - obs_data_set_string(settings, "video_settings", vEncCustom); - obs_data_set_int(settings, "audio_bitrate", aBitrate); - obs_data_set_string(settings, "audio_encoder", aEncoder); - obs_data_set_int(settings, "audio_encoder_id", aEncoderId); - obs_data_set_string(settings, "audio_settings", aEncCustom); - - if (rescale && rescaleRes && *rescaleRes) { - int width; - int height; - int val = sscanf(rescaleRes, "%dx%d", &width, &height); - - if (val == 2 && width && height) { - obs_data_set_int(settings, "scale_width", width); - obs_data_set_int(settings, "scale_height", height); - } - } - - obs_output_set_mixers(fileOutput, aMixes); - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - obs_output_update(fileOutput, settings); -} - -static inline void SetEncoderName(obs_encoder_t *encoder, const char *name, const char *defaultName) -{ - obs_encoder_set_name(encoder, (name && *name) ? name : defaultName); -} - -inline void AdvancedOutput::UpdateAudioSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - const char *audioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - - bool is_multitrack_output = allowsMultiTrack(); - - OBSDataAutoRelease settings[MAX_AUDIO_MIXES]; - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - const char *name = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - string def_name = "Track"; - def_name += to_string((int)i + 1); - SetEncoderName(recordTrack[i], name, def_name.c_str()); - SetEncoderName(streamTrack[i], name, def_name.c_str()); - } - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - int track = (int)(i + 1); - settings[i] = obs_data_create(); - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, recAudioEncoder)); - - obs_encoder_update(recordTrack[i], settings[i]); - - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, audioEncoder)); - - if (!is_multitrack_output) { - if (track == streamTrackIndex || track == vodTrackIndex) { - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings[i], "bitrate"); - obs_service_apply_encoder_settings(main->GetService(), nullptr, settings[i]); - - if (!enforceBitrate) - obs_data_set_int(settings[i], "bitrate", bitrate); - } - } - - if (track == streamTrackIndex) - obs_encoder_update(streamAudioEnc, settings[i]); - if (track == vodTrackIndex) - obs_encoder_update(streamArchiveEnc, settings[i]); - } else { - obs_encoder_update(streamTrack[i], settings[i]); - } - } -} - -void AdvancedOutput::SetupOutputs() -{ - obs_encoder_set_video(videoStreaming, obs_get_video()); - if (videoRecording) - obs_encoder_set_video(videoRecording, obs_get_video()); - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - obs_encoder_set_audio(streamTrack[i], obs_get_audio()); - obs_encoder_set_audio(recordTrack[i], obs_get_audio()); - } - obs_encoder_set_audio(streamAudioEnc, obs_get_audio()); - obs_encoder_set_audio(streamArchiveEnc, obs_get_audio()); - - SetupStreaming(); - - if (ffmpegOutput) - SetupFFmpeg(); - else - SetupRecording(); -} - -int AdvancedOutput::GetAudioBitrate(size_t i, const char *id) const -{ - static const char *names[] = { - "Track1Bitrate", "Track2Bitrate", "Track3Bitrate", "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", - }; - int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]); - return FindClosestAvailableAudioBitrate(id, bitrate); -} - -inline std::optional AdvancedOutput::VodTrackMixerIdx(obs_service_t *service) -{ - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - bool vodTrackEnabled = config_get_bool(main->Config(), "AdvOut", "VodTrackEnabled"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) { - vodTrackEnabled = enableForCustomServer ? vodTrackEnabled : false; - } else { - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *service = obs_data_get_string(settings, "service"); - if (!ServiceSupportsVodTrack(service)) - vodTrackEnabled = false; - } - - if (vodTrackEnabled && streamTrackIndex != vodTrackIndex) - return {vodTrackIndex - 1}; - return std::nullopt; -} - -inline void AdvancedOutput::SetupVodTrack(obs_service_t *service) -{ - if (VodTrackMixerIdx(service).has_value()) - obs_output_set_audio_encoder(streamOutput, streamArchiveEnc, 1); - else - clear_archive_encoder(streamOutput, ADV_ARCHIVE_NAME); -} - -std::shared_future AdvancedOutput::SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) -{ - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - - bool is_multitrack_output = allowsMultiTrack(); - - if (!useStreamEncoder || (!ffmpegOutput && !obs_output_active(fileOutput))) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - const char *audio_encoder_id = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "adv_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - int idx = 0; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - return true; - }; - - return SetupMultitrackVideo(service, audio_encoder_id, static_cast(streamTrackIndex), - VodTrackMixerIdx(service), [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -bool AdvancedOutput::StartStreaming(obs_service_t *service) -{ - obs_output_set_service(streamOutput, service); - - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_int(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_int(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - bool is_rtmp = false; - obs_service_t *service_obj = main->GetService(); - const char *protocol = obs_service_get_protocol(service_obj); - if (protocol) { - if (astrcmpi_n(protocol, RTMP_PROTOCOL, strlen(RTMP_PROTOCOL)) == 0) - is_rtmp = true; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - if (is_rtmp) { - SetupVodTrack(service); - } - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -bool AdvancedOutput::StartRecording() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - bool splitFile; - const char *splitFileType; - int splitFileTime; - int splitFileSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) { - UpdateRecordingSettings(); - } - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - splitFile = config_get_bool(main->Config(), "AdvOut", "RecSplitFile"); - - string strPath = GetRecordingFilename(path, recFormat, noSpace, overwriteIfExists, filenameFormat, - ffmpegRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, ffmpegRecording ? "url" : "path", strPath.c_str()); - - if (splitFile) { - splitFileType = config_get_string(main->Config(), "AdvOut", "RecSplitFileType"); - splitFileTime = (astrcmpi(splitFileType, "Time") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileTime") - : 0; - splitFileSize = (astrcmpi(splitFileType, "Size") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileSize") - : 0; - string ext = GetFormatExt(recFormat); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", filenameFormat); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_bool(settings, "allow_overwrite", overwriteIfExists); - obs_data_set_bool(settings, "split_file", true); - obs_data_set_int(settings, "max_time_sec", splitFileTime * 60); - obs_data_set_int(settings, "max_size_mb", splitFileSize); - } - - obs_output_update(fileOutput, settings); - } - - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool AdvancedOutput::StartReplayBuffer() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - const char *rbPrefix; - const char *rbSuffix; - int rbTime; - int rbSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - rbTime = config_get_int(main->Config(), "AdvOut", "RecRBTime"); - rbSize = config_get_int(main->Config(), "AdvOut", "RecRBSize"); - - string f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(recFormat); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usesBitrate ? 0 : rbSize); - - obs_output_update(replayBuffer, settings); - } - - if (!obs_output_start(replayBuffer)) { - QString error_reason; - const char *error = obs_output_get_last_error(replayBuffer); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), error_reason); - return false; - } - - return true; -} - -void AdvancedOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void AdvancedOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void AdvancedOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool AdvancedOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool AdvancedOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool AdvancedOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - -void BasicOutputHandler::SetupAutoRemux(const char *&container) -{ - bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux"); - if (autoRemux && strcmp(container, "mp4") == 0) - container = "mkv"; -} - -std::string BasicOutputHandler::GetRecordingFilename(const char *path, const char *container, bool noSpace, - bool overwrite, const char *format, bool ffmpeg) -{ - if (!ffmpeg) - SetupAutoRemux(container); - - string dst = GetOutputFilename(path, container, noSpace, overwrite, format); - lastRecordingPath = dst; - return dst; -} - -extern std::string DeserializeConfigText(const char *text); - -std::shared_future BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service, std::string audio_encoder_id, - size_t main_audio_mixer, - std::optional vod_track_mixer, - std::function)> continuation) -{ - auto start_streaming_guard = std::make_shared(); - if (!multitrackVideo) { - continuation(std::nullopt); - return start_streaming_guard->GetFuture(); - } - - multitrackVideoActive = false; - - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - bool is_custom = strncmp("rtmp_custom", obs_service_get_type(service), 11) == 0; - - std::optional custom_config = std::nullopt; - if (config_get_bool(main->Config(), "Stream1", "MultitrackVideoConfigOverrideEnabled")) - custom_config = DeserializeConfigText( - config_get_string(main->Config(), "Stream1", "MultitrackVideoConfigOverride")); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - QString key = obs_data_get_string(settings, "key"); - - const char *service_name = ""; - if (is_custom && obs_data_has_user_value(settings, "service_name")) { - service_name = obs_data_get_string(settings, "service_name"); - } else if (!is_custom) { - service_name = obs_data_get_string(settings, "service"); - } - - std::optional custom_rtmp_url; - std::optional use_rtmps; - auto server = obs_data_get_string(settings, "server"); - if (strncmp(server, "auto", 4) != 0) { - custom_rtmp_url = server; - } else { - QString server_ = server; - use_rtmps = server_.contains("rtmps", Qt::CaseInsensitive); - } - - auto service_custom_server = obs_data_get_bool(settings, "using_custom_server"); - if (custom_rtmp_url.has_value()) { - blog(LOG_INFO, "Using %sserver '%s'", service_custom_server ? "custom " : "", custom_rtmp_url->c_str()); - } - - auto maximum_aggregate_bitrate = - config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto") - ? std::nullopt - : std::make_optional( - config_get_int(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrate")); - - auto maximum_video_tracks = config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracksAuto") - ? std::nullopt - : std::make_optional(config_get_int( - main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracks")); - - auto stream_dump_config = GenerateMultitrackVideoStreamDumpConfig(); - - auto continue_on_main_thread = [&, start_streaming_guard, service = OBSService{service}, - continuation = - std::move(continuation)](std::optional error) { - if (error) { - OBSDataAutoRelease service_settings = obs_service_get_settings(service); - auto multitrack_video_name = QTStr("Basic.Settings.Stream.MultitrackVideoLabel"); - if (obs_data_has_user_value(service_settings, "multitrack_video_name")) { - multitrack_video_name = obs_data_get_string(service_settings, "multitrack_video_name"); - } - - multitrackVideoActive = false; - if (!error->ShowDialog(main, multitrack_video_name)) - return continuation(false); - return continuation(std::nullopt); - } - - multitrackVideoActive = true; - - auto signal_handler = multitrackVideo->StreamingSignalHandler(); - - streamDelayStarting.Connect(signal_handler, "starting", OBSStreamStarting, this); - streamStopping.Connect(signal_handler, "stopping", OBSStreamStopping, this); - - startStreaming.Connect(signal_handler, "start", OBSStartStreaming, this); - stopStreaming.Connect(signal_handler, "stop", OBSStopStreaming, this); - return continuation(true); - }; - - QThreadPool::globalInstance()->start([=, multitrackVideo = multitrackVideo.get(), - service_name = std::string{service_name}, service = OBSService{service}, - stream_dump_config = OBSData{stream_dump_config}, - start_streaming_guard = start_streaming_guard]() mutable { - std::optional error; - try { - multitrackVideo->PrepareStreaming(main, service_name.c_str(), service, custom_rtmp_url, key, - audio_encoder_id.c_str(), maximum_aggregate_bitrate, - maximum_video_tracks, custom_config, stream_dump_config, - main_audio_mixer, vod_track_mixer, use_rtmps); - } catch (const MultitrackVideoError &error_) { - error.emplace(error_); - } - - QMetaObject::invokeMethod(main, [=] { continue_on_main_thread(error); }); - }); - - return start_streaming_guard->GetFuture(); -} - -OBSDataAutoRelease BasicOutputHandler::GenerateMultitrackVideoStreamDumpConfig() -{ - auto stream_dump_enabled = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpEnabled"); - - if (!stream_dump_enabled) - return nullptr; - - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - bool useMP4 = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpAsMP4"); - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, useMP4 ? "mp4" : "flv", noSpace, overwriteIfExists, f.c_str(), - // never remux stream dump - false); - obs_data_set_string(settings, "path", strPath.c_str()); - - if (useMP4) { - obs_data_set_bool(settings, "use_mp4", true); - obs_data_set_string(settings, "muxer_settings", "write_encoder_info=1"); - } - - return settings; -} - -BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main) -{ - return new SimpleOutput(main); -} - -BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main) -{ - return new AdvancedOutput(main); -} diff --git a/frontend/utility/BasicOutputHandler.cpp b/frontend/utility/BasicOutputHandler.cpp index 30d203174..7488c4662 100644 --- a/frontend/utility/BasicOutputHandler.cpp +++ b/frontend/utility/BasicOutputHandler.cpp @@ -1,14 +1,15 @@ -#include -#include -#include -#include -#include +#include "BasicOutputHandler.hpp" +#include "AdvancedOutput.hpp" +#include "SimpleOutput.hpp" + +#include +#include +#include +#include + #include -#include "audio-encoders.hpp" -#include "multitrack-video-error.hpp" -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" -#include "window-basic-vcam.hpp" + +#include using namespace std; @@ -20,11 +21,7 @@ volatile bool recording_paused = false; volatile bool replaybuf_active = false; volatile bool virtualcam_active = false; -#define RTMP_PROTOCOL "rtmp" -#define SRT_PROTOCOL "srt" -#define RIST_PROTOCOL "rist" - -static void OBSStreamStarting(void *data, calldata_t *params) +void OBSStreamStarting(void *data, calldata_t *params) { BasicOutputHandler *output = static_cast(data); obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); @@ -37,7 +34,7 @@ static void OBSStreamStarting(void *data, calldata_t *params) QMetaObject::invokeMethod(output->main, "StreamDelayStarting", Q_ARG(int, sec)); } -static void OBSStreamStopping(void *data, calldata_t *params) +void OBSStreamStopping(void *data, calldata_t *params) { BasicOutputHandler *output = static_cast(data); obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); @@ -49,7 +46,7 @@ static void OBSStreamStopping(void *data, calldata_t *params) QMetaObject::invokeMethod(output->main, "StreamDelayStopping", Q_ARG(int, sec)); } -static void OBSStartStreaming(void *data, calldata_t * /* params */) +void OBSStartStreaming(void *data, calldata_t * /* params */) { BasicOutputHandler *output = static_cast(data); output->streamingActive = true; @@ -57,7 +54,7 @@ static void OBSStartStreaming(void *data, calldata_t * /* params */) QMetaObject::invokeMethod(output->main, "StreamingStart"); } -static void OBSStopStreaming(void *data, calldata_t *params) +void OBSStopStreaming(void *data, calldata_t *params) { BasicOutputHandler *output = static_cast(data); int code = (int)calldata_int(params, "code"); @@ -72,7 +69,7 @@ static void OBSStopStreaming(void *data, calldata_t *params) QMetaObject::invokeMethod(output->main, "StreamingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); } -static void OBSStartRecording(void *data, calldata_t * /* params */) +void OBSStartRecording(void *data, calldata_t * /* params */) { BasicOutputHandler *output = static_cast(data); @@ -81,7 +78,7 @@ static void OBSStartRecording(void *data, calldata_t * /* params */) QMetaObject::invokeMethod(output->main, "RecordingStart"); } -static void OBSStopRecording(void *data, calldata_t *params) +void OBSStopRecording(void *data, calldata_t *params) { BasicOutputHandler *output = static_cast(data); int code = (int)calldata_int(params, "code"); @@ -95,13 +92,13 @@ static void OBSStopRecording(void *data, calldata_t *params) QMetaObject::invokeMethod(output->main, "RecordingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); } -static void OBSRecordStopping(void *data, calldata_t * /* params */) +void OBSRecordStopping(void *data, calldata_t * /* params */) { BasicOutputHandler *output = static_cast(data); QMetaObject::invokeMethod(output->main, "RecordStopping"); } -static void OBSRecordFileChanged(void *data, calldata_t *params) +void OBSRecordFileChanged(void *data, calldata_t *params) { BasicOutputHandler *output = static_cast(data); const char *next_file = calldata_string(params, "next_file"); @@ -113,7 +110,7 @@ static void OBSRecordFileChanged(void *data, calldata_t *params) output->lastRecordingPath = next_file; } -static void OBSStartReplayBuffer(void *data, calldata_t * /* params */) +void OBSStartReplayBuffer(void *data, calldata_t * /* params */) { BasicOutputHandler *output = static_cast(data); @@ -122,7 +119,7 @@ static void OBSStartReplayBuffer(void *data, calldata_t * /* params */) QMetaObject::invokeMethod(output->main, "ReplayBufferStart"); } -static void OBSStopReplayBuffer(void *data, calldata_t *params) +void OBSStopReplayBuffer(void *data, calldata_t *params) { BasicOutputHandler *output = static_cast(data); int code = (int)calldata_int(params, "code"); @@ -132,13 +129,13 @@ static void OBSStopReplayBuffer(void *data, calldata_t *params) QMetaObject::invokeMethod(output->main, "ReplayBufferStop", Q_ARG(int, code)); } -static void OBSReplayBufferStopping(void *data, calldata_t * /* params */) +void OBSReplayBufferStopping(void *data, calldata_t * /* params */) { BasicOutputHandler *output = static_cast(data); QMetaObject::invokeMethod(output->main, "ReplayBufferStopping"); } -static void OBSReplayBufferSaved(void *data, calldata_t * /* params */) +void OBSReplayBufferSaved(void *data, calldata_t * /* params */) { BasicOutputHandler *output = static_cast(data); QMetaObject::invokeMethod(output->main, "ReplayBufferSaved", Qt::QueuedConnection); @@ -169,71 +166,7 @@ static void OBSDeactivateVirtualCam(void *data, calldata_t * /* params */) output->DestroyVirtualCamView(); } -/* ------------------------------------------------------------------------ */ - -struct StartMultitrackVideoStreamingGuard { - StartMultitrackVideoStreamingGuard() { future = guard.get_future().share(); }; - ~StartMultitrackVideoStreamingGuard() { guard.set_value(); } - - std::shared_future GetFuture() const { return future; } - - static std::shared_future MakeReadyFuture() - { - StartMultitrackVideoStreamingGuard guard; - return guard.GetFuture(); - } - -private: - std::promise guard; - std::shared_future future; -}; - -/* ------------------------------------------------------------------------ */ - -static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleAACEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static bool CreateSimpleOpusEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleOpusEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, - const char *prot_test2 = nullptr) -{ - return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && - (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; -} - -static bool return_first_id(void *data, const char *id) +bool return_first_id(void *data, const char *id) { const char **output = (const char **)data; @@ -241,7 +174,7 @@ static bool return_first_id(void *data, const char *id) return false; } -static const char *GetStreamOutputType(const obs_service_t *service) +const char *GetStreamOutputType(const obs_service_t *service) { const char *protocol = obs_service_get_protocol(service); const char *output = nullptr; @@ -284,9 +217,7 @@ static const char *GetStreamOutputType(const obs_service_t *service) return nullptr; } -/* ------------------------------------------------------------------------ */ - -inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) +BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) { if (main->vcamEnabled) { virtualCam = obs_output_create(VIRTUAL_CAM_ID, "virtualcam_output", nullptr, nullptr); @@ -450,581 +381,6 @@ void BasicOutputHandler::DestroyVirtualCameraScene() vCamSourceSceneItem = nullptr; } -/* ------------------------------------------------------------------------ */ - -struct SimpleOutput : BasicOutputHandler { - OBSEncoder audioStreaming; - OBSEncoder videoStreaming; - OBSEncoder audioRecording; - OBSEncoder audioArchive; - OBSEncoder videoRecording; - OBSEncoder audioTrack[MAX_AUDIO_MIXES]; - - string videoEncoder; - string videoQuality; - bool usingRecordingPreset = false; - bool recordingConfigured = false; - bool ffmpegOutput = false; - bool lowCPUx264 = false; - - SimpleOutput(OBSBasic *main_); - - int CalcCRF(int crf); - - void UpdateRecordingSettings_x264_crf(int crf); - void UpdateRecordingSettings_qsv11(int crf, bool av1); - void UpdateRecordingSettings_nvenc(int cqp); - void UpdateRecordingSettings_nvenc_hevc_av1(int cqp); - void UpdateRecordingSettings_amd_cqp(int cqp); - void UpdateRecordingSettings_apple(int quality); -#ifdef ENABLE_HEVC - void UpdateRecordingSettings_apple_hevc(int quality); -#endif - void UpdateRecordingSettings(); - void UpdateRecordingAudioSettings(); - virtual void Update() override; - - void SetupOutputs() override; - int GetAudioBitrate() const; - - void LoadRecordingPreset_Lossy(const char *encoder); - void LoadRecordingPreset_Lossless(); - void LoadRecordingPreset(); - - void LoadStreamingPreset_Lossy(const char *encoder); - - void UpdateRecording(); - bool ConfigureRecording(bool useReplayBuffer); - - bool IsVodTrackEnabled(obs_service_t *service); - void SetupVodTrack(obs_service_t *service); - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; -}; - -void SimpleOutput::LoadRecordingPreset_Lossless() -{ - fileOutput = obs_output_create("ffmpeg_output", "simple_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(simple output)"; - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "format_name", "avi"); - obs_data_set_string(settings, "video_encoder", "utvideo"); - obs_data_set_string(settings, "audio_encoder", "pcm_s16le"); - - obs_output_update(fileOutput, settings); -} - -void SimpleOutput::LoadRecordingPreset_Lossy(const char *encoderId) -{ - videoRecording = obs_video_encoder_create(encoderId, "simple_video_recording", nullptr, nullptr); - if (!videoRecording) - throw "Failed to create video recording encoder (simple output)"; - obs_encoder_release(videoRecording); -} - -void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId) -{ - videoStreaming = obs_video_encoder_create(encoderId, "simple_video_stream", nullptr, nullptr); - if (!videoStreaming) - throw "Failed to create video streaming encoder (simple output)"; - obs_encoder_release(videoStreaming); -} - -/* mistakes have been made to lead us to this. */ -const char *get_simple_output_encoder(const char *encoder) -{ - if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - return "obs_qsv11_v2"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - return "obs_qsv11_av1"; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - return "h264_texture_amf"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - return "h265_texture_amf"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - return "av1_texture_amf"; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - return EncoderAvailable("obs_nvenc_h264_tex") ? "obs_nvenc_h264_tex" : "ffmpeg_nvenc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - return EncoderAvailable("obs_nvenc_hevc_tex") ? "obs_nvenc_hevc_tex" : "ffmpeg_hevc_nvenc"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - return "obs_nvenc_av1_tex"; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.avc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.hevc"; -#endif - } - - return "obs_x264"; -} - -void SimpleOutput::LoadRecordingPreset() -{ - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "RecEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "RecAudioEncoder"); - - videoEncoder = encoder; - videoQuality = quality; - ffmpegOutput = false; - - if (strcmp(quality, "Stream") == 0) { - videoRecording = videoStreaming; - audioRecording = audioStreaming; - usingRecordingPreset = false; - return; - - } else if (strcmp(quality, "Lossless") == 0) { - LoadRecordingPreset_Lossless(); - usingRecordingPreset = true; - ffmpegOutput = true; - return; - - } else { - lowCPUx264 = false; - - if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) - lowCPUx264 = true; - LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder)); - usingRecordingPreset = true; - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioRecording, 192, "simple_opus_recording", 0); - else - success = CreateSimpleAACEncoder(audioRecording, 192, "simple_aac_recording", 0); - - if (!success) - throw "Failed to create audio recording encoder " - "(simple output)"; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[23]; - if (strcmp(audio_encoder, "opus") == 0) { - snprintf(name, sizeof name, "simple_opus_recording%d", i); - success = CreateSimpleOpusEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } else { - snprintf(name, sizeof name, "simple_aac_recording%d", i); - success = CreateSimpleAACEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } - if (!success) - throw "Failed to create multi-track audio recording encoder " - "(simple output)"; - } - } -} - -#define SIMPLE_ARCHIVE_NAME "simple_archive_audio" - -SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - - LoadStreamingPreset_Lossy(get_simple_output_encoder(encoder)); - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioStreaming, GetAudioBitrate(), "simple_opus", 0); - else - success = CreateSimpleAACEncoder(audioStreaming, GetAudioBitrate(), "simple_aac", 0); - - if (!success) - throw "Failed to create audio streaming encoder (simple output)"; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - else - success = CreateSimpleAACEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - - if (!success) - throw "Failed to create audio archive encoder (simple output)"; - - LoadRecordingPreset(); - - if (!ffmpegOutput) { - bool useReplayBuffer = config_get_bool(main->Config(), "SimpleOutput", "RecRB"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool use_native = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(use_native ? "mp4_output" : "ffmpeg_muxer", "simple_file_output", - nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(simple output)"; - } - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); -} - -int SimpleOutput::GetAudioBitrate() const -{ - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); - - if (strcmp(audio_encoder, "opus") == 0) - return FindClosestAvailableSimpleOpusBitrate(bitrate); - - return FindClosestAvailableSimpleAACBitrate(bitrate); -} - -void SimpleOutput::Update() -{ - OBSDataAutoRelease videoSettings = obs_data_create(); - OBSDataAutoRelease audioSettings = obs_data_create(); - - int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); - int audioBitrate = GetAudioBitrate(); - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - const char *custom = config_get_string(main->Config(), "SimpleOutput", "x264Settings"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *encoder_id = obs_encoder_get_id(videoStreaming); - const char *presetType; - const char *preset; - - if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - presetType = "AMDPreset"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset2"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - presetType = "NVENCPreset2"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - presetType = "AMDAV1Preset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - presetType = "NVENCPreset2"; - - } else { - presetType = "Preset"; - } - - preset = config_get_string(main->Config(), "SimpleOutput", presetType); - - /* Only use preset2 for legacy/FFmpeg NVENC Encoder. */ - if (strncmp(encoder_id, "ffmpeg_", 7) == 0 && strcmp(presetType, "NVENCPreset2") == 0) { - obs_data_set_string(videoSettings, "preset2", preset); - } else { - obs_data_set_string(videoSettings, "preset", preset); - } - - obs_data_set_string(videoSettings, "rate_control", "CBR"); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - - if (advanced) - obs_data_set_string(videoSettings, "x264opts", custom); - - obs_data_set_string(audioSettings, "rate_control", "CBR"); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - - obs_service_apply_encoder_settings(main->GetService(), videoSettings, audioSettings); - - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - } - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, videoSettings); - obs_encoder_update(audioStreaming, audioSettings); - obs_encoder_update(audioArchive, audioSettings); -} - -void SimpleOutput::UpdateRecordingAudioSettings() -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", 192); - obs_data_set_string(settings, "rate_control", "CBR"); - - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv || strcmp(quality, "Stream") == 0) { - obs_encoder_update(audioRecording, settings); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_update(audioTrack[i], settings); - } - } - } -} - -#define CROSS_DIST_CUTOFF 2000.0 - -int SimpleOutput::CalcCRF(int crf) -{ - int cx = config_get_uint(main->Config(), "Video", "OutputCX"); - int cy = config_get_uint(main->Config(), "Video", "OutputCY"); - double fCX = double(cx); - double fCY = double(cy); - - if (lowCPUx264) - crf -= 2; - - double crossDist = sqrt(fCX * fCX + fCY * fCY); - double crfResReduction = fmin(CROSS_DIST_CUTOFF, crossDist) / CROSS_DIST_CUTOFF; - crfResReduction = (1.0 - crfResReduction) * 10.0; - - return crf - int(crfResReduction); -} - -void SimpleOutput::UpdateRecordingSettings_x264_crf(int crf) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "crf", crf); - obs_data_set_bool(settings, "use_bufsize", true); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", lowCPUx264 ? "ultrafast" : "veryfast"); - - obs_encoder_update(videoRecording, settings); -} - -static bool icq_available(obs_encoder_t *encoder) -{ - obs_properties_t *props = obs_encoder_properties(encoder); - obs_property_t *p = obs_properties_get(props, "rate_control"); - bool icq_found = false; - - size_t num = obs_property_list_item_count(p); - for (size_t i = 0; i < num; i++) { - const char *val = obs_property_list_item_string(p, i); - if (strcmp(val, "ICQ") == 0) { - icq_found = true; - break; - } - } - - obs_properties_destroy(props); - return icq_found; -} - -void SimpleOutput::UpdateRecordingSettings_qsv11(int crf, bool av1) -{ - bool icq = icq_available(videoRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "profile", "high"); - - if (icq && !av1) { - obs_data_set_string(settings, "rate_control", "ICQ"); - obs_data_set_int(settings, "icq_quality", crf); - } else { - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_int(settings, "cqp", crf); - } - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc_hevc_av1(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_apple(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} - -#ifdef ENABLE_HEVC -void SimpleOutput::UpdateRecordingSettings_apple_hevc(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} -#endif - -void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", "quality"); - obs_data_set_int(settings, "cqp", cqp); - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings() -{ - bool ultra_hq = (videoQuality == "HQ"); - int crf = CalcCRF(ultra_hq ? 16 : 23); - - if (astrcmp_n(videoEncoder.c_str(), "x264", 4) == 0) { - UpdateRecordingSettings_x264_crf(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV) { - UpdateRecordingSettings_qsv11(crf, false); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV_AV1) { - UpdateRecordingSettings_qsv11(crf, true); - - } else if (videoEncoder == SIMPLE_ENCODER_AMD) { - UpdateRecordingSettings_amd_cqp(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_AMD_HEVC) { - UpdateRecordingSettings_amd_cqp(crf); -#endif - - } else if (videoEncoder == SIMPLE_ENCODER_AMD_AV1) { - UpdateRecordingSettings_amd_cqp(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_NVENC) { - UpdateRecordingSettings_nvenc(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_HEVC) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); -#endif - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_AV1) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_H264) { - /* These are magic numbers. 0 - 100, more is better. */ - UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_HEVC) { - UpdateRecordingSettings_apple_hevc(ultra_hq ? 70 : 50); -#endif - } - UpdateRecordingAudioSettings(); -} - -inline void SimpleOutput::SetupOutputs() -{ - SimpleOutput::Update(); - obs_encoder_set_video(videoStreaming, obs_get_video()); - obs_encoder_set_audio(audioStreaming, obs_get_audio()); - obs_encoder_set_audio(audioArchive, obs_get_audio()); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (usingRecordingPreset) { - if (ffmpegOutput) { - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - } else { - obs_encoder_set_video(videoRecording, obs_get_video()); - if (flv) { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_set_audio(audioTrack[i], obs_get_audio()); - } - } - } - } - } else { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } -} - const char *FindAudioEncoderFromCodec(const char *type) { const char *alt_enc_id = nullptr; @@ -1040,74 +396,7 @@ const char *FindAudioEncoderFromCodec(const char *type) return nullptr; } -std::shared_future SimpleOutput::SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation) -{ - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - auto audio_bitrate = GetAudioBitrate(); - auto vod_track_mixer = IsVodTrackEnabled(service) ? std::optional{1} : std::nullopt; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "simple_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, audioStreaming, 0); - obs_output_set_service(streamOutput, service); - return true; - }; - - return SetupMultitrackVideo(service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0, vod_track_mixer, - [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -static inline bool ServiceSupportsVodTrack(const char *service); - -static void clear_archive_encoder(obs_output_t *output, const char *expected_name) +void clear_archive_encoder(obs_output_t *output, const char *expected_name) { obs_encoder_t *last = obs_output_get_audio_encoder(output, 1); bool clear = false; @@ -1123,1252 +412,6 @@ static void clear_archive_encoder(obs_output_t *output, const char *expected_nam obs_output_set_audio_encoder(output, nullptr, 1); } -bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service) -{ - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enable = config_get_bool(main->Config(), "SimpleOutput", "VodTrackEnabled"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *name = obs_data_get_string(settings, "service"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) - return enableForCustomServer ? enable : false; - else - return advanced && enable && ServiceSupportsVodTrack(name); -} - -void SimpleOutput::SetupVodTrack(obs_service_t *service) -{ - if (IsVodTrackEnabled(service)) - obs_output_set_audio_encoder(streamOutput, audioArchive, 1); - else - clear_archive_encoder(streamOutput, SIMPLE_ARCHIVE_NAME); -} - -bool SimpleOutput::StartStreaming(obs_service_t *service) -{ - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_uint(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_uint(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - - if (!multitrackVideo || !multitrackVideoActive) - SetupVodTrack(service); - - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -void SimpleOutput::UpdateRecording() -{ - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - int idx = 0; - int idx2 = 0; - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - - if (replayBufferActive || recordingActive) - return; - - if (usingRecordingPreset) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(streamOutput)) { - Update(); - } - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput) { - obs_output_set_video_encoder(fileOutput, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(fileOutput, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, audioTrack[i], idx++); - } - } - } - } - if (replayBuffer) { - obs_output_set_video_encoder(replayBuffer, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(replayBuffer, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(replayBuffer, audioTrack[i], idx2++); - } - } - } - } - - recordingConfigured = true; -} - -bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) -{ - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - const char *format = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *mux = config_get_string(main->Config(), "SimpleOutput", "MuxerCustom"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - int rbTime = config_get_int(main->Config(), "SimpleOutput", "RecRBTime"); - int rbSize = config_get_int(main->Config(), "SimpleOutput", "RecRBSize"); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - - bool is_fragmented = strncmp(format, "fragmented", 10) == 0; - bool is_lossless = videoQuality == "Lossless"; - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - if (updateReplayBuffer) { - f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(format); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usingRecordingPreset ? rbSize : 0); - } else { - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, ffmpegOutput ? "avi" : format, noSpace, overwriteIfExists, - f.c_str(), ffmpegOutput); - obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str()); - if (ffmpegOutput) - obs_output_set_mixers(fileOutput, tracks); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && !is_lossless && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented && !is_lossless) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - if (updateReplayBuffer) - obs_output_update(replayBuffer, settings); - else - obs_output_update(fileOutput, settings); - - return true; -} - -bool SimpleOutput::StartRecording() -{ - UpdateRecording(); - if (!ConfigureRecording(false)) - return false; - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool SimpleOutput::StartReplayBuffer() -{ - UpdateRecording(); - if (!ConfigureRecording(true)) - return false; - if (!obs_output_start(replayBuffer)) { - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), QTStr("Output.StartFailedGeneric")); - return false; - } - - return true; -} - -void SimpleOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void SimpleOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void SimpleOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool SimpleOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool SimpleOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool SimpleOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - -struct AdvancedOutput : BasicOutputHandler { - OBSEncoder streamAudioEnc; - OBSEncoder streamArchiveEnc; - OBSEncoder streamTrack[MAX_AUDIO_MIXES]; - OBSEncoder recordTrack[MAX_AUDIO_MIXES]; - OBSEncoder videoStreaming; - OBSEncoder videoRecording; - - bool ffmpegOutput; - bool ffmpegRecording; - bool useStreamEncoder; - bool useStreamAudioEncoder; - bool usesBitrate = false; - - AdvancedOutput(OBSBasic *main_); - - inline void UpdateStreamSettings(); - inline void UpdateRecordingSettings(); - inline void UpdateAudioSettings(); - virtual void Update() override; - - inline std::optional VodTrackMixerIdx(obs_service_t *service); - inline void SetupVodTrack(obs_service_t *service); - - inline void SetupStreaming(); - inline void SetupRecording(); - inline void SetupFFmpeg(); - void SetupOutputs() override; - int GetAudioBitrate(size_t i, const char *id) const; - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; - bool allowsMultiTrack(); -}; - -static OBSData GetDataFromJsonFile(const char *jsonFile) -{ - const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); - - const OBSProfile ¤tProfile = basic->GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(jsonFile); - - OBSDataAutoRelease data = nullptr; - - if (!jsonFilePath.empty()) { - BPtr jsonData = os_quick_read_utf8_file(jsonFilePath.u8string().c_str()); - - if (!!jsonData) { - data = obs_data_create_from_json(jsonData); - } - } - - if (!data) { - data = obs_data_create(); - } - - return data.Get(); -} - -static void ApplyEncoderDefaults(OBSData &settings, const obs_encoder_t *encoder) -{ - OBSData dataRet = obs_encoder_get_defaults(encoder); - obs_data_release(dataRet); - - if (!!settings) - obs_data_apply(dataRet, settings); - settings = std::move(dataRet); -} - -#define ADV_ARCHIVE_NAME "adv_archive_audio" - -#ifdef __APPLE__ -static void translate_macvth264_encoder(const char *&encoder) -{ - if (strcmp(encoder, "vt_h264_hw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264.gva"; - } else if (strcmp(encoder, "vt_h264_sw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264"; - } -} -#endif - -AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *recType = config_get_string(main->Config(), "AdvOut", "RecType"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - const char *streamAudioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recordEncoder = config_get_string(main->Config(), "AdvOut", "RecEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); -#ifdef __APPLE__ - translate_macvth264_encoder(streamEncoder); - translate_macvth264_encoder(recordEncoder); -#endif - - ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - ffmpegRecording = ffmpegOutput && config_get_bool(main->Config(), "AdvOut", "FFOutputToFile"); - useStreamEncoder = astrcmpi(recordEncoder, "none") == 0; - useStreamAudioEncoder = astrcmpi(recAudioEncoder, "none") == 0; - - OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); - OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); - - if (ffmpegOutput) { - fileOutput = obs_output_create("ffmpeg_output", "adv_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(advanced output)"; - } else { - bool useReplayBuffer = config_get_bool(main->Config(), "AdvOut", "RecRB"); - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool native_muxer = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(native_muxer ? "mp4_output" : "ffmpeg_muxer", "adv_file_output", nullptr, - nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(advanced output)"; - - if (!useStreamEncoder) { - videoRecording = obs_video_encoder_create(recordEncoder, "advanced_video_recording", - recordEncSettings, nullptr); - if (!videoRecording) - throw "Failed to create recording video " - "encoder (advanced output)"; - obs_encoder_release(videoRecording); - } - } - - videoStreaming = obs_video_encoder_create(streamEncoder, "advanced_video_stream", streamEncSettings, nullptr); - if (!videoStreaming) - throw "Failed to create streaming video encoder " - "(advanced output)"; - obs_encoder_release(videoStreaming); - - const char *rate_control = - obs_data_get_string(useStreamEncoder ? streamEncSettings : recordEncSettings, "rate_control"); - if (!rate_control) - rate_control = ""; - usesBitrate = astrcmpi(rate_control, "CBR") == 0 || astrcmpi(rate_control, "VBR") == 0 || - astrcmpi(rate_control, "ABR") == 0; - - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[19]; - snprintf(name, sizeof(name), "adv_record_audio_%d", i); - - recordTrack[i] = obs_audio_encoder_create(useStreamAudioEncoder ? streamAudioEncoder : recAudioEncoder, - name, nullptr, i, nullptr); - - if (!recordTrack[i]) { - throw "Failed to create audio encoder " - "(advanced output)"; - } - - obs_encoder_release(recordTrack[i]); - - snprintf(name, sizeof(name), "adv_stream_audio_%d", i); - streamTrack[i] = obs_audio_encoder_create(streamAudioEncoder, name, nullptr, i, nullptr); - - if (!streamTrack[i]) { - throw "Failed to create streaming audio encoders " - "(advanced output)"; - } - - obs_encoder_release(streamTrack[i]); - } - - std::string id; - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - streamAudioEnc = - obs_audio_encoder_create(streamAudioEncoder, "adv_stream_audio", nullptr, streamTrackIndex, nullptr); - if (!streamAudioEnc) - throw "Failed to create streaming audio encoder " - "(advanced output)"; - obs_encoder_release(streamAudioEnc); - - id = ""; - int vodTrack = config_get_int(main->Config(), "AdvOut", "VodTrackIndex") - 1; - streamArchiveEnc = obs_audio_encoder_create(streamAudioEncoder, ADV_ARCHIVE_NAME, nullptr, vodTrack, nullptr); - if (!streamArchiveEnc) - throw "Failed to create archive audio encoder " - "(advanced output)"; - obs_encoder_release(streamArchiveEnc); - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); - recordFileChanged.Connect(obs_output_get_signal_handler(fileOutput), "file_changed", OBSRecordFileChanged, - this); -} - -void AdvancedOutput::UpdateStreamSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - bool dynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - - OBSData settings = GetDataFromJsonFile("streamEncoder.json"); - ApplyEncoderDefaults(settings, videoStreaming); - - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings, "bitrate"); - int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(settings, "bitrate", bitrate); - } - - int enforced_keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - if (keyint_sec != 0 && keyint_sec < enforced_keyint_sec) - obs_data_set_int(settings, "keyint_sec", keyint_sec); - } else { - blog(LOG_WARNING, "User is ignoring service settings."); - } - - if (dynBitrate && strstr(streamEncoder, "nvenc") != nullptr) - obs_data_set_bool(settings, "lookahead", false); - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, settings); -} - -inline void AdvancedOutput::UpdateRecordingSettings() -{ - OBSData settings = GetDataFromJsonFile("recordEncoder.json"); - obs_encoder_update(videoRecording, settings); -} - -void AdvancedOutput::Update() -{ - UpdateStreamSettings(); - if (!useStreamEncoder && !ffmpegOutput) - UpdateRecordingSettings(); - UpdateAudioSettings(); -} - -static inline bool ServiceSupportsVodTrack(const char *service) -{ - static const char *vodTrackServices[] = {"Twitch"}; - - for (const char *vodTrackService : vodTrackServices) { - if (astrcmpi(vodTrackService, service) == 0) - return true; - } - - return false; -} - -inline bool AdvancedOutput::allowsMultiTrack() -{ - const char *protocol = nullptr; - obs_service_t *service_obj = main->GetService(); - protocol = obs_service_get_protocol(service_obj); - if (!protocol) - return false; - return astrcmpi_n(protocol, SRT_PROTOCOL, strlen(SRT_PROTOCOL)) == 0 || - astrcmpi_n(protocol, RIST_PROTOCOL, strlen(RIST_PROTOCOL)) == 0; -} - -inline void AdvancedOutput::SetupStreaming() -{ - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RescaleFilter"); - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - bool is_multitrack_output = allowsMultiTrack(); - - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - obs_encoder_set_scaled_size(videoStreaming, cx, cy); - obs_encoder_set_gpu_scale_type(videoStreaming, (obs_scale_type)rescaleFilter); - - const char *id = obs_service_get_id(main->GetService()); - if (strcmp(id, "rtmp_custom") == 0) { - OBSDataAutoRelease settings = obs_data_create(); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - obs_encoder_update(videoStreaming, settings); - } -} - -inline void AdvancedOutput::SetupRecording() -{ - const char *path = config_get_string(main->Config(), "AdvOut", "RecFilePath"); - const char *mux = config_get_string(main->Config(), "AdvOut", "RecMuxerCustom"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RecRescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RecRescaleFilter"); - int tracks; - - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); - - bool is_fragmented = strncmp(recFormat, "fragmented", 10) == 0; - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv) - tracks = config_get_int(main->Config(), "AdvOut", "FLVTrack"); - else - tracks = config_get_int(main->Config(), "AdvOut", "RecTracks"); - - OBSDataAutoRelease settings = obs_data_create(); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - - /* Hack to allow recordings without any audio tracks selected. It is no - * longer possible to select such a configuration in settings, but legacy - * configurations might still have this configured and we don't want to - * just break them. */ - if (tracks == 0) - tracks = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - - if (useStreamEncoder) { - obs_output_set_video_encoder(fileOutput, videoStreaming); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoStreaming); - } else { - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - obs_encoder_set_scaled_size(videoRecording, cx, cy); - obs_encoder_set_gpu_scale_type(videoRecording, (obs_scale_type)rescaleFilter); - obs_output_set_video_encoder(fileOutput, videoRecording); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoRecording); - } - - if (!flv) { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[i], idx); - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[i], idx); - idx++; - } - } - } else if (flv && tracks != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[tracks - 1], idx); - - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[tracks - 1], idx); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - obs_data_set_string(settings, "path", path); - obs_output_update(fileOutput, settings); - if (replayBuffer) - obs_output_update(replayBuffer, settings); -} - -inline void AdvancedOutput::SetupFFmpeg() -{ - const char *url = config_get_string(main->Config(), "AdvOut", "FFURL"); - int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate"); - int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize"); - bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "FFRescaleRes"); - const char *formatName = config_get_string(main->Config(), "AdvOut", "FFFormat"); - const char *mimeType = config_get_string(main->Config(), "AdvOut", "FFFormatMimeType"); - const char *muxCustom = config_get_string(main->Config(), "AdvOut", "FFMCustom"); - const char *vEncoder = config_get_string(main->Config(), "AdvOut", "FFVEncoder"); - int vEncoderId = config_get_int(main->Config(), "AdvOut", "FFVEncoderId"); - const char *vEncCustom = config_get_string(main->Config(), "AdvOut", "FFVCustom"); - int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate"); - int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes"); - const char *aEncoder = config_get_string(main->Config(), "AdvOut", "FFAEncoder"); - int aEncoderId = config_get_int(main->Config(), "AdvOut", "FFAEncoderId"); - const char *aEncCustom = config_get_string(main->Config(), "AdvOut", "FFACustom"); - - OBSDataArrayAutoRelease audio_names = obs_data_array_create(); - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - - const char *audioName = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - OBSDataAutoRelease item = obs_data_create(); - obs_data_set_string(item, "name", audioName); - obs_data_array_push_back(audio_names, item); - } - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_array(settings, "audio_names", audio_names); - obs_data_set_string(settings, "url", url); - obs_data_set_string(settings, "format_name", formatName); - obs_data_set_string(settings, "format_mime_type", mimeType); - obs_data_set_string(settings, "muxer_settings", muxCustom); - obs_data_set_int(settings, "gop_size", gopSize); - obs_data_set_int(settings, "video_bitrate", vBitrate); - obs_data_set_string(settings, "video_encoder", vEncoder); - obs_data_set_int(settings, "video_encoder_id", vEncoderId); - obs_data_set_string(settings, "video_settings", vEncCustom); - obs_data_set_int(settings, "audio_bitrate", aBitrate); - obs_data_set_string(settings, "audio_encoder", aEncoder); - obs_data_set_int(settings, "audio_encoder_id", aEncoderId); - obs_data_set_string(settings, "audio_settings", aEncCustom); - - if (rescale && rescaleRes && *rescaleRes) { - int width; - int height; - int val = sscanf(rescaleRes, "%dx%d", &width, &height); - - if (val == 2 && width && height) { - obs_data_set_int(settings, "scale_width", width); - obs_data_set_int(settings, "scale_height", height); - } - } - - obs_output_set_mixers(fileOutput, aMixes); - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - obs_output_update(fileOutput, settings); -} - -static inline void SetEncoderName(obs_encoder_t *encoder, const char *name, const char *defaultName) -{ - obs_encoder_set_name(encoder, (name && *name) ? name : defaultName); -} - -inline void AdvancedOutput::UpdateAudioSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - const char *audioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - - bool is_multitrack_output = allowsMultiTrack(); - - OBSDataAutoRelease settings[MAX_AUDIO_MIXES]; - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - const char *name = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - string def_name = "Track"; - def_name += to_string((int)i + 1); - SetEncoderName(recordTrack[i], name, def_name.c_str()); - SetEncoderName(streamTrack[i], name, def_name.c_str()); - } - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - int track = (int)(i + 1); - settings[i] = obs_data_create(); - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, recAudioEncoder)); - - obs_encoder_update(recordTrack[i], settings[i]); - - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, audioEncoder)); - - if (!is_multitrack_output) { - if (track == streamTrackIndex || track == vodTrackIndex) { - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings[i], "bitrate"); - obs_service_apply_encoder_settings(main->GetService(), nullptr, settings[i]); - - if (!enforceBitrate) - obs_data_set_int(settings[i], "bitrate", bitrate); - } - } - - if (track == streamTrackIndex) - obs_encoder_update(streamAudioEnc, settings[i]); - if (track == vodTrackIndex) - obs_encoder_update(streamArchiveEnc, settings[i]); - } else { - obs_encoder_update(streamTrack[i], settings[i]); - } - } -} - -void AdvancedOutput::SetupOutputs() -{ - obs_encoder_set_video(videoStreaming, obs_get_video()); - if (videoRecording) - obs_encoder_set_video(videoRecording, obs_get_video()); - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - obs_encoder_set_audio(streamTrack[i], obs_get_audio()); - obs_encoder_set_audio(recordTrack[i], obs_get_audio()); - } - obs_encoder_set_audio(streamAudioEnc, obs_get_audio()); - obs_encoder_set_audio(streamArchiveEnc, obs_get_audio()); - - SetupStreaming(); - - if (ffmpegOutput) - SetupFFmpeg(); - else - SetupRecording(); -} - -int AdvancedOutput::GetAudioBitrate(size_t i, const char *id) const -{ - static const char *names[] = { - "Track1Bitrate", "Track2Bitrate", "Track3Bitrate", "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", - }; - int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]); - return FindClosestAvailableAudioBitrate(id, bitrate); -} - -inline std::optional AdvancedOutput::VodTrackMixerIdx(obs_service_t *service) -{ - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - bool vodTrackEnabled = config_get_bool(main->Config(), "AdvOut", "VodTrackEnabled"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) { - vodTrackEnabled = enableForCustomServer ? vodTrackEnabled : false; - } else { - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *service = obs_data_get_string(settings, "service"); - if (!ServiceSupportsVodTrack(service)) - vodTrackEnabled = false; - } - - if (vodTrackEnabled && streamTrackIndex != vodTrackIndex) - return {vodTrackIndex - 1}; - return std::nullopt; -} - -inline void AdvancedOutput::SetupVodTrack(obs_service_t *service) -{ - if (VodTrackMixerIdx(service).has_value()) - obs_output_set_audio_encoder(streamOutput, streamArchiveEnc, 1); - else - clear_archive_encoder(streamOutput, ADV_ARCHIVE_NAME); -} - -std::shared_future AdvancedOutput::SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) -{ - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - - bool is_multitrack_output = allowsMultiTrack(); - - if (!useStreamEncoder || (!ffmpegOutput && !obs_output_active(fileOutput))) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - const char *audio_encoder_id = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "adv_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - int idx = 0; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - return true; - }; - - return SetupMultitrackVideo(service, audio_encoder_id, static_cast(streamTrackIndex), - VodTrackMixerIdx(service), [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -bool AdvancedOutput::StartStreaming(obs_service_t *service) -{ - obs_output_set_service(streamOutput, service); - - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_int(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_int(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - bool is_rtmp = false; - obs_service_t *service_obj = main->GetService(); - const char *protocol = obs_service_get_protocol(service_obj); - if (protocol) { - if (astrcmpi_n(protocol, RTMP_PROTOCOL, strlen(RTMP_PROTOCOL)) == 0) - is_rtmp = true; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - if (is_rtmp) { - SetupVodTrack(service); - } - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -bool AdvancedOutput::StartRecording() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - bool splitFile; - const char *splitFileType; - int splitFileTime; - int splitFileSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) { - UpdateRecordingSettings(); - } - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - splitFile = config_get_bool(main->Config(), "AdvOut", "RecSplitFile"); - - string strPath = GetRecordingFilename(path, recFormat, noSpace, overwriteIfExists, filenameFormat, - ffmpegRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, ffmpegRecording ? "url" : "path", strPath.c_str()); - - if (splitFile) { - splitFileType = config_get_string(main->Config(), "AdvOut", "RecSplitFileType"); - splitFileTime = (astrcmpi(splitFileType, "Time") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileTime") - : 0; - splitFileSize = (astrcmpi(splitFileType, "Size") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileSize") - : 0; - string ext = GetFormatExt(recFormat); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", filenameFormat); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_bool(settings, "allow_overwrite", overwriteIfExists); - obs_data_set_bool(settings, "split_file", true); - obs_data_set_int(settings, "max_time_sec", splitFileTime * 60); - obs_data_set_int(settings, "max_size_mb", splitFileSize); - } - - obs_output_update(fileOutput, settings); - } - - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool AdvancedOutput::StartReplayBuffer() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - const char *rbPrefix; - const char *rbSuffix; - int rbTime; - int rbSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - rbTime = config_get_int(main->Config(), "AdvOut", "RecRBTime"); - rbSize = config_get_int(main->Config(), "AdvOut", "RecRBSize"); - - string f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(recFormat); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usesBitrate ? 0 : rbSize); - - obs_output_update(replayBuffer, settings); - } - - if (!obs_output_start(replayBuffer)) { - QString error_reason; - const char *error = obs_output_get_last_error(replayBuffer); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), error_reason); - return false; - } - - return true; -} - -void AdvancedOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void AdvancedOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void AdvancedOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool AdvancedOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool AdvancedOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool AdvancedOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - void BasicOutputHandler::SetupAutoRemux(const char *&container) { bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux"); diff --git a/frontend/utility/BasicOutputHandler.hpp b/frontend/utility/BasicOutputHandler.hpp index f7178f19d..7d041304a 100644 --- a/frontend/utility/BasicOutputHandler.hpp +++ b/frontend/utility/BasicOutputHandler.hpp @@ -1,10 +1,15 @@ #pragma once -#include -#include -#include +#include -#include "multitrack-video-output.hpp" +#include +#include + +#include + +#define RTMP_PROTOCOL "rtmp" +#define SRT_PROTOCOL "srt" +#define RIST_PROTOCOL "rist" class OBSBasic; @@ -58,7 +63,7 @@ struct BasicOutputHandler { OBSSignal replayBufferStopping; OBSSignal replayBufferSaved; - inline BasicOutputHandler(OBSBasic *main_); + BasicOutputHandler(OBSBasic *main_); virtual ~BasicOutputHandler(){}; @@ -103,3 +108,39 @@ protected: BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main); BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main); + +void OBSStreamStarting(void *data, calldata_t *params); +void OBSStreamStopping(void *data, calldata_t *params); +void OBSStartStreaming(void *data, calldata_t *params); +void OBSStopStreaming(void *data, calldata_t *params); +void OBSStartRecording(void *data, calldata_t *params); +void OBSStopRecording(void *data, calldata_t *params); +void OBSRecordStopping(void *data, calldata_t *params); +void OBSRecordFileChanged(void *data, calldata_t *params); +void OBSStartReplayBuffer(void *data, calldata_t *params); +void OBSStopReplayBuffer(void *data, calldata_t *params); +void OBSReplayBufferStopping(void *data, calldata_t *params); +void OBSReplayBufferSaved(void *data, calldata_t *params); + +inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, + const char *prot_test2 = nullptr) +{ + return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && + (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; +} + +const char *GetStreamOutputType(const obs_service_t *service); + +inline bool ServiceSupportsVodTrack(const char *service) +{ + static const char *vodTrackServices[] = {"Twitch"}; + + for (const char *vodTrackService : vodTrackServices) { + if (astrcmpi(vodTrackService, service) == 0) + return true; + } + + return false; +} + +void clear_archive_encoder(obs_output_t *output, const char *expected_name); diff --git a/frontend/utility/QuickTransition.cpp b/frontend/utility/QuickTransition.cpp index a17137b2b..f0179a6cd 100644 --- a/frontend/utility/QuickTransition.cpp +++ b/frontend/utility/QuickTransition.cpp @@ -15,27 +15,11 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include +#include "QuickTransition.hpp" + +#include + #include -#include -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" -#include "window-basic-vcam-config.hpp" -#include "display-helpers.hpp" -#include "window-namedialog.hpp" -#include "menu-button.hpp" - -#include "obs-hotkey.h" - -using namespace std; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(QuickTransition); static inline QString MakeQuickTransitionText(QuickTransition *qt) { @@ -51,54 +35,6 @@ static inline QString MakeQuickTransitionText(QuickTransition *qt) return name; } -void OBSBasic::InitDefaultTransitions() -{ - std::vector transitions; - size_t idx = 0; - const char *id; - - /* automatically add transitions that have no configuration (things - * such as cut/fade/etc) */ - while (obs_enum_transition_types(idx++, &id)) { - if (!obs_is_source_configurable(id)) { - const char *name = obs_source_get_display_name(id); - - OBSSourceAutoRelease tr = obs_source_create_private(id, name, NULL); - InitTransition(tr); - transitions.emplace_back(tr); - - if (strcmp(id, "fade_transition") == 0) - fadeTransition = tr; - else if (strcmp(id, "cut_transition") == 0) - cutTransition = tr; - } - } - - for (OBSSource &tr : transitions) { - ui->transitions->addItem(QT_UTF8(obs_source_get_name(tr)), QVariant::fromValue(OBSSource(tr))); - } -} - -void OBSBasic::AddQuickTransitionHotkey(QuickTransition *qt) -{ - DStr hotkeyId; - QString hotkeyName; - - dstr_printf(hotkeyId, "OBSBasic.QuickTransition.%d", qt->id); - hotkeyName = QTStr("QuickTransitions.HotkeyName").arg(MakeQuickTransitionText(qt)); - - auto quickTransition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - int id = (int)(uintptr_t)data; - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - if (pressed) - QMetaObject::invokeMethod(main, "TriggerQuickTransition", Qt::QueuedConnection, Q_ARG(int, id)); - }; - - qt->hotkey = obs_hotkey_register_frontend(hotkeyId->array, QT_TO_UTF8(hotkeyName), quickTransition, - (void *)(uintptr_t)qt->id); -} - void QuickTransition::SourceRenamed(void *param, calldata_t *) { QuickTransition *qt = reinterpret_cast(param); @@ -107,1593 +43,3 @@ void QuickTransition::SourceRenamed(void *param, calldata_t *) obs_hotkey_set_description(qt->hotkey, QT_TO_UTF8(hotkeyName)); } - -void OBSBasic::TriggerQuickTransition(int id) -{ - QuickTransition *qt = GetQuickTransition(id); - - if (qt && previewProgramMode) { - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (GetCurrentTransition() != qt->source) { - OverrideTransition(qt->source); - overridingTransition = true; - } - - TransitionToScene(source, false, true, qt->duration, qt->fadeToBlack); - } -} - -void OBSBasic::RemoveQuickTransitionHotkey(QuickTransition *qt) -{ - obs_hotkey_unregister(qt->hotkey); -} - -void OBSBasic::InitTransition(obs_source_t *transition) -{ - auto onTransitionStop = [](void *data, calldata_t *) { - OBSBasic *window = (OBSBasic *)data; - QMetaObject::invokeMethod(window, "TransitionStopped", Qt::QueuedConnection); - }; - - auto onTransitionFullStop = [](void *data, calldata_t *) { - OBSBasic *window = (OBSBasic *)data; - QMetaObject::invokeMethod(window, "TransitionFullyStopped", Qt::QueuedConnection); - }; - - signal_handler_t *handler = obs_source_get_signal_handler(transition); - signal_handler_connect(handler, "transition_video_stop", onTransitionStop, this); - signal_handler_connect(handler, "transition_stop", onTransitionFullStop, this); -} - -static inline OBSSource GetTransitionComboItem(QComboBox *combo, int idx) -{ - return combo->itemData(idx).value(); -} - -void OBSBasic::CreateDefaultQuickTransitions() -{ - /* non-configurable transitions are always available, so add them - * to the "default quick transitions" list */ - quickTransitions.emplace_back(cutTransition, 300, quickTransitionIdCounter++); - quickTransitions.emplace_back(fadeTransition, 300, quickTransitionIdCounter++); - quickTransitions.emplace_back(fadeTransition, 300, quickTransitionIdCounter++, true); -} - -void OBSBasic::LoadQuickTransitions(obs_data_array_t *array) -{ - size_t count = obs_data_array_count(array); - - quickTransitionIdCounter = 1; - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - OBSDataArrayAutoRelease hotkeys = obs_data_get_array(data, "hotkeys"); - const char *name = obs_data_get_string(data, "name"); - int duration = obs_data_get_int(data, "duration"); - int id = obs_data_get_int(data, "id"); - bool toBlack = obs_data_get_bool(data, "fade_to_black"); - - if (id) { - obs_source_t *source = FindTransition(name); - if (source) { - quickTransitions.emplace_back(source, duration, id, toBlack); - - if (quickTransitionIdCounter <= id) - quickTransitionIdCounter = id + 1; - - int idx = (int)quickTransitions.size() - 1; - AddQuickTransitionHotkey(&quickTransitions[idx]); - obs_hotkey_load(quickTransitions[idx].hotkey, hotkeys); - } - } - } -} - -obs_data_array_t *OBSBasic::SaveQuickTransitions() -{ - obs_data_array_t *array = obs_data_array_create(); - - for (QuickTransition &qt : quickTransitions) { - OBSDataAutoRelease data = obs_data_create(); - OBSDataArrayAutoRelease hotkeys = obs_hotkey_save(qt.hotkey); - - obs_data_set_string(data, "name", obs_source_get_name(qt.source)); - obs_data_set_int(data, "duration", qt.duration); - obs_data_set_array(data, "hotkeys", hotkeys); - obs_data_set_int(data, "id", qt.id); - obs_data_set_bool(data, "fade_to_black", qt.fadeToBlack); - - obs_data_array_push_back(array, data); - } - - return array; -} - -obs_source_t *OBSBasic::FindTransition(const char *name) -{ - for (int i = 0; i < ui->transitions->count(); i++) { - OBSSource tr = ui->transitions->itemData(i).value(); - if (!tr) - continue; - - const char *trName = obs_source_get_name(tr); - if (strcmp(trName, name) == 0) - return tr; - } - - return nullptr; -} - -void OBSBasic::TransitionToScene(OBSScene scene, bool force) -{ - obs_source_t *source = obs_scene_get_source(scene); - TransitionToScene(source, force); -} - -void OBSBasic::TransitionStopped() -{ - if (swapScenesMode) { - OBSSource scene = OBSGetStrongRef(swapScene); - if (scene) - SetCurrentScene(scene); - } - - EnableTransitionWidgets(true); - UpdatePreviewProgramIndicators(); - - OnEvent(OBS_FRONTEND_EVENT_TRANSITION_STOPPED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - - swapScene = nullptr; -} - -void OBSBasic::OverrideTransition(OBSSource transition) -{ - OBSSourceAutoRelease oldTransition = obs_get_output_source(0); - - if (transition != oldTransition) { - obs_transition_swap_begin(transition, oldTransition); - obs_set_output_source(0, transition); - obs_transition_swap_end(transition, oldTransition); - } -} - -void OBSBasic::TransitionFullyStopped() -{ - if (overridingTransition) { - OverrideTransition(GetCurrentTransition()); - overridingTransition = false; - } -} - -void OBSBasic::TransitionToScene(OBSSource source, bool force, bool quickTransition, int quickDuration, bool black, - bool manual) -{ - obs_scene_t *scene = obs_scene_from_source(source); - bool usingPreviewProgram = IsPreviewProgramMode(); - if (!scene) - return; - - if (usingPreviewProgram) { - if (!tBarActive) - lastProgramScene = programScene; - programScene = OBSGetWeakRef(source); - - if (!force && !black) { - OBSSource lastScene = OBSGetStrongRef(lastProgramScene); - - if (!sceneDuplicationMode && lastScene == source) - return; - - if (swapScenesMode && lastScene && lastScene != GetCurrentSceneSource()) - swapScene = lastProgramScene; - } - } - - if (usingPreviewProgram && sceneDuplicationMode) { - scene = obs_scene_duplicate(scene, obs_source_get_name(obs_scene_get_source(scene)), - editPropertiesMode ? OBS_SCENE_DUP_PRIVATE_COPY - : OBS_SCENE_DUP_PRIVATE_REFS); - source = obs_scene_get_source(scene); - } - - OBSSourceAutoRelease transition = obs_get_output_source(0); - if (!transition) { - if (usingPreviewProgram && sceneDuplicationMode) - obs_scene_release(scene); - return; - } - - float t = obs_transition_get_time(transition); - bool stillTransitioning = t < 1.0f && t > 0.0f; - - // If actively transitioning, block new transitions from starting - if (usingPreviewProgram && stillTransitioning) - goto cleanup; - - if (usingPreviewProgram) { - if (!black && !manual) { - const char *sceneName = obs_source_get_name(source); - blog(LOG_INFO, "User switched Program to scene '%s'", sceneName); - - } else if (black && !prevFTBSource) { - OBSSourceAutoRelease target = obs_transition_get_active_source(transition); - const char *sceneName = obs_source_get_name(target); - blog(LOG_INFO, "User faded from scene '%s' to black", sceneName); - - } else if (black && prevFTBSource) { - const char *sceneName = obs_source_get_name(prevFTBSource); - blog(LOG_INFO, "User faded from black to scene '%s'", sceneName); - - } else if (manual) { - const char *sceneName = obs_source_get_name(source); - blog(LOG_INFO, "User started manual transition to scene '%s'", sceneName); - } - } - - if (force) { - obs_transition_set(transition, source); - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - } else { - int duration = ui->transitionDuration->value(); - - /* check for scene override */ - OBSSource trOverride = GetOverrideTransition(source); - - if (trOverride && !overridingTransition && !quickTransition) { - transition = std::move(trOverride); - duration = GetOverrideTransitionDuration(source); - OverrideTransition(transition.Get()); - overridingTransition = true; - } - - if (black && !prevFTBSource) { - prevFTBSource = source; - source = nullptr; - } else if (black && prevFTBSource) { - source = prevFTBSource; - prevFTBSource = nullptr; - } else if (!black) { - prevFTBSource = nullptr; - } - - if (quickTransition) - duration = quickDuration; - - enum obs_transition_mode mode = manual ? OBS_TRANSITION_MODE_MANUAL : OBS_TRANSITION_MODE_AUTO; - - EnableTransitionWidgets(false); - - bool success = obs_transition_start(transition, mode, duration, source); - - if (!success) - TransitionFullyStopped(); - } - -cleanup: - if (usingPreviewProgram && sceneDuplicationMode) - obs_scene_release(scene); -} - -static inline void SetComboTransition(QComboBox *combo, obs_source_t *tr) -{ - int idx = combo->findData(QVariant::fromValue(tr)); - if (idx != -1) { - combo->blockSignals(true); - combo->setCurrentIndex(idx); - combo->blockSignals(false); - } -} - -void OBSBasic::SetTransition(OBSSource transition) -{ - OBSSourceAutoRelease oldTransition = obs_get_output_source(0); - - if (oldTransition && transition) { - obs_transition_swap_begin(transition, oldTransition); - if (transition != GetCurrentTransition()) - SetComboTransition(ui->transitions, transition); - obs_set_output_source(0, transition); - obs_transition_swap_end(transition, oldTransition); - } else { - obs_set_output_source(0, transition); - } - - bool fixed = transition ? obs_transition_fixed(transition) : false; - ui->transitionDurationLabel->setVisible(!fixed); - ui->transitionDuration->setVisible(!fixed); - - bool configurable = transition ? obs_source_configurable(transition) : false; - ui->transitionRemove->setEnabled(configurable); - ui->transitionProps->setEnabled(configurable); - - OnEvent(OBS_FRONTEND_EVENT_TRANSITION_CHANGED); -} - -OBSSource OBSBasic::GetCurrentTransition() -{ - return ui->transitions->currentData().value(); -} - -void OBSBasic::on_transitions_currentIndexChanged(int) -{ - OBSSource transition = GetCurrentTransition(); - SetTransition(transition); -} - -void OBSBasic::AddTransition(const char *id) -{ - string name; - QString placeHolderText = QT_UTF8(obs_source_get_display_name(id)); - QString format = placeHolderText + " (%1)"; - obs_source_t *source = nullptr; - int i = 1; - - while ((FindTransition(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("TransitionNameDlg.Title"), QTStr("TransitionNameDlg.Text"), - name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - AddTransition(id); - return; - } - - source = FindTransition(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - AddTransition(id); - return; - } - - source = obs_source_create_private(id, name.c_str(), NULL); - InitTransition(source); - ui->transitions->addItem(QT_UTF8(name.c_str()), QVariant::fromValue(OBSSource(source))); - ui->transitions->setCurrentIndex(ui->transitions->count() - 1); - CreatePropertiesWindow(source); - obs_source_release(source); - - OnEvent(OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED); - - ClearQuickTransitionWidgets(); - RefreshQuickTransitions(); - } -} - -void OBSBasic::on_transitionAdd_clicked() -{ - bool foundConfigurableTransitions = false; - QMenu menu(this); - size_t idx = 0; - const char *id; - - while (obs_enum_transition_types(idx++, &id)) { - if (obs_is_source_configurable(id)) { - const char *name = obs_source_get_display_name(id); - QAction *action = new QAction(name, this); - - connect(action, &QAction::triggered, [this, id]() { AddTransition(id); }); - - menu.addAction(action); - foundConfigurableTransitions = true; - } - } - - if (foundConfigurableTransitions) - menu.exec(QCursor::pos()); -} - -void OBSBasic::on_transitionRemove_clicked() -{ - OBSSource tr = GetCurrentTransition(); - - if (!tr || !obs_source_configurable(tr) || !QueryRemoveSource(tr)) - return; - - int idx = ui->transitions->findData(QVariant::fromValue(tr)); - if (idx == -1) - return; - - for (size_t i = quickTransitions.size(); i > 0; i--) { - QuickTransition &qt = quickTransitions[i - 1]; - if (qt.source == tr) { - if (qt.button) - qt.button->deleteLater(); - RemoveQuickTransitionHotkey(&qt); - quickTransitions.erase(quickTransitions.begin() + i - 1); - } - } - - ui->transitions->removeItem(idx); - - OnEvent(OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED); - - ClearQuickTransitionWidgets(); - RefreshQuickTransitions(); -} - -void OBSBasic::RenameTransition(OBSSource transition) -{ - string name; - QString placeHolderText = QT_UTF8(obs_source_get_name(transition)); - obs_source_t *source = nullptr; - - bool accepted = NameDialog::AskForName(this, QTStr("TransitionNameDlg.Title"), QTStr("TransitionNameDlg.Text"), - name, placeHolderText); - - if (!accepted) - return; - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - RenameTransition(transition); - return; - } - - source = FindTransition(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - RenameTransition(transition); - return; - } - - obs_source_set_name(transition, name.c_str()); - int idx = ui->transitions->findData(QVariant::fromValue(transition)); - if (idx != -1) { - ui->transitions->setItemText(idx, QT_UTF8(name.c_str())); - - OnEvent(OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED); - - ClearQuickTransitionWidgets(); - RefreshQuickTransitions(); - } -} - -void OBSBasic::on_transitionProps_clicked() -{ - OBSSource source = GetCurrentTransition(); - - if (!obs_source_configurable(source)) - return; - - auto properties = [&]() { - CreatePropertiesWindow(source); - }; - - QMenu menu(this); - - QAction *action = new QAction(QTStr("Rename"), &menu); - connect(action, &QAction::triggered, [this, source]() { RenameTransition(source); }); - menu.addAction(action); - - action = new QAction(QTStr("Properties"), &menu); - connect(action, &QAction::triggered, properties); - menu.addAction(action); - - menu.exec(QCursor::pos()); -} - -void OBSBasic::on_transitionDuration_valueChanged() -{ - OnEvent(OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED); -} - -QuickTransition *OBSBasic::GetQuickTransition(int id) -{ - for (QuickTransition &qt : quickTransitions) { - if (qt.id == id) - return &qt; - } - - return nullptr; -} - -int OBSBasic::GetQuickTransitionIdx(int id) -{ - for (int idx = 0; idx < (int)quickTransitions.size(); idx++) { - QuickTransition &qt = quickTransitions[idx]; - - if (qt.id == id) - return idx; - } - - return -1; -} - -void OBSBasic::SetCurrentScene(obs_scene_t *scene, bool force) -{ - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, force); -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -void OBSBasic::SetCurrentScene(OBSSource scene, bool force) -{ - if (!IsPreviewProgramMode()) { - TransitionToScene(scene, force); - } else { - OBSSource actualLastScene = OBSGetStrongRef(lastScene); - if (actualLastScene != scene) { - if (scene) - obs_source_inc_showing(scene); - if (actualLastScene) - obs_source_dec_showing(actualLastScene); - lastScene = OBSGetWeakRef(scene); - } - } - - if (obs_scene_get_source(GetCurrentScene()) != scene) { - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene itemScene = GetOBSRef(item); - obs_source_t *source = obs_scene_get_source(itemScene); - - if (source == scene) { - ui->scenes->blockSignals(true); - currentScene = itemScene.Get(); - ui->scenes->setCurrentItem(item); - ui->scenes->blockSignals(false); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - break; - } - } - } - - UpdateContextBar(true); - UpdatePreviewProgramIndicators(); - - if (scene) { - bool userSwitched = (!force && !disableSaving); - blog(LOG_INFO, "%s to scene '%s'", userSwitched ? "User switched" : "Switched", - obs_source_get_name(scene)); - } -} - -void OBSBasic::CreateProgramDisplay() -{ - program = new OBSQTDisplay(); - - program->setContextMenuPolicy(Qt::CustomContextMenu); - connect(program.data(), &QWidget::customContextMenuRequested, this, &OBSBasic::ProgramViewContextMenuRequested); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizeProgram(ovi.base_width, ovi.base_height); - }; - - connect(program.data(), &OBSQTDisplay::DisplayResized, displayResize); - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderProgram, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizeProgram(ovi.base_width, ovi.base_height); - }; - - connect(program.data(), &OBSQTDisplay::DisplayCreated, addDisplay); - - program->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); -} - -void OBSBasic::TransitionClicked() -{ - if (previewProgramMode) - TransitionToScene(GetCurrentScene()); -} - -#define T_BAR_PRECISION 1024 -#define T_BAR_PRECISION_F ((float)T_BAR_PRECISION) -#define T_BAR_CLAMP (T_BAR_PRECISION / 10) - -void OBSBasic::CreateProgramOptions() -{ - programOptions = new QWidget(); - QVBoxLayout *layout = new QVBoxLayout(); - layout->setSpacing(4); - - QPushButton *configTransitions = new QPushButton(); - configTransitions->setProperty("class", "icon-dots-vert"); - - QHBoxLayout *mainButtonLayout = new QHBoxLayout(); - mainButtonLayout->setSpacing(2); - - transitionButton = new QPushButton(QTStr("Transition")); - transitionButton->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - QHBoxLayout *quickTransitions = new QHBoxLayout(); - quickTransitions->setSpacing(2); - - QPushButton *addQuickTransition = new QPushButton(); - addQuickTransition->setProperty("class", "icon-plus"); - - QLabel *quickTransitionsLabel = new QLabel(QTStr("QuickTransitions")); - quickTransitionsLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); - - quickTransitions->addWidget(quickTransitionsLabel); - quickTransitions->addWidget(addQuickTransition); - - mainButtonLayout->addWidget(transitionButton); - mainButtonLayout->addWidget(configTransitions); - - tBar = new SliderIgnoreClick(Qt::Horizontal); - tBar->setMinimum(0); - tBar->setMaximum(T_BAR_PRECISION - 1); - - tBar->setProperty("class", "slider-tbar"); - - connect(tBar, &QSlider::valueChanged, this, &OBSBasic::TBarChanged); - connect(tBar, &QSlider::sliderReleased, this, &OBSBasic::TBarReleased); - - layout->addStretch(0); - layout->addLayout(mainButtonLayout); - layout->addLayout(quickTransitions); - layout->addWidget(tBar); - layout->addStretch(0); - - programOptions->setLayout(layout); - - auto onAdd = [this]() { - QScopedPointer menu(CreateTransitionMenu(this, nullptr)); - menu->exec(QCursor::pos()); - }; - - auto onConfig = [this]() { - QMenu menu(this); - QAction *action; - - auto toggleEditProperties = [this]() { - editPropertiesMode = !editPropertiesMode; - - OBSSource actualScene = OBSGetStrongRef(programScene); - if (actualScene) - TransitionToScene(actualScene, true); - }; - - auto toggleSwapScenesMode = [this]() { - swapScenesMode = !swapScenesMode; - }; - - auto toggleSceneDuplication = [this]() { - sceneDuplicationMode = !sceneDuplicationMode; - - OBSSource actualScene = OBSGetStrongRef(programScene); - if (actualScene) - TransitionToScene(actualScene, true); - }; - - auto showToolTip = [&]() { - QAction *act = menu.activeAction(); - QToolTip::showText(QCursor::pos(), act->toolTip(), &menu, menu.actionGeometry(act)); - }; - - action = menu.addAction(QTStr("QuickTransitions.DuplicateScene")); - action->setToolTip(QTStr("QuickTransitions.DuplicateSceneTT")); - action->setCheckable(true); - action->setChecked(sceneDuplicationMode); - connect(action, &QAction::triggered, toggleSceneDuplication); - connect(action, &QAction::hovered, showToolTip); - - action = menu.addAction(QTStr("QuickTransitions.EditProperties")); - action->setToolTip(QTStr("QuickTransitions.EditPropertiesTT")); - action->setCheckable(true); - action->setChecked(editPropertiesMode); - action->setEnabled(sceneDuplicationMode); - connect(action, &QAction::triggered, toggleEditProperties); - connect(action, &QAction::hovered, showToolTip); - - action = menu.addAction(QTStr("QuickTransitions.SwapScenes")); - action->setToolTip(QTStr("QuickTransitions.SwapScenesTT")); - action->setCheckable(true); - action->setChecked(swapScenesMode); - connect(action, &QAction::triggered, toggleSwapScenesMode); - connect(action, &QAction::hovered, showToolTip); - - menu.exec(QCursor::pos()); - }; - - connect(transitionButton.data(), &QAbstractButton::clicked, this, &OBSBasic::TransitionClicked); - connect(addQuickTransition, &QAbstractButton::clicked, onAdd); - connect(configTransitions, &QAbstractButton::clicked, onConfig); -} - -void OBSBasic::TBarReleased() -{ - int val = tBar->value(); - - OBSSourceAutoRelease transition = obs_get_output_source(0); - - if ((tBar->maximum() - val) <= T_BAR_CLAMP) { - obs_transition_set_manual_time(transition, 1.0f); - tBar->blockSignals(true); - tBar->setValue(0); - tBar->blockSignals(false); - tBarActive = false; - EnableTransitionWidgets(true); - - OBSSourceAutoRelease target = obs_transition_get_active_source(transition); - const char *sceneName = obs_source_get_name(target); - blog(LOG_INFO, "Manual transition to scene '%s' finished", sceneName); - } else if (val <= T_BAR_CLAMP) { - obs_transition_set_manual_time(transition, 0.0f); - TransitionFullyStopped(); - tBar->blockSignals(true); - tBar->setValue(0); - tBar->blockSignals(false); - tBarActive = false; - EnableTransitionWidgets(true); - programScene = lastProgramScene; - blog(LOG_INFO, "Manual transition cancelled"); - } - - tBar->clearFocus(); -} - -static bool ValidTBarTransition(OBSSource transition) -{ - if (!transition) - return false; - - QString id = QT_UTF8(obs_source_get_id(transition)); - - if (id == "cut_transition" || id == "obs_stinger_transition") - return false; - - return true; -} - -void OBSBasic::TBarChanged(int value) -{ - OBSSourceAutoRelease transition = obs_get_output_source(0); - - if (!tBarActive) { - OBSSource sceneSource = GetCurrentSceneSource(); - OBSSource tBarTr = GetOverrideTransition(sceneSource); - - if (!ValidTBarTransition(tBarTr)) { - tBarTr = GetCurrentTransition(); - - if (!ValidTBarTransition(tBarTr)) - tBarTr = FindTransition(obs_source_get_display_name("fade_transition")); - - OverrideTransition(tBarTr); - overridingTransition = true; - - transition = std::move(tBarTr); - } - - obs_transition_set_manual_torque(transition, 8.0f, 0.05f); - TransitionToScene(sceneSource, false, false, false, 0, true); - tBarActive = true; - } - - obs_transition_set_manual_time(transition, (float)value / T_BAR_PRECISION_F); - - OnEvent(OBS_FRONTEND_EVENT_TBAR_VALUE_CHANGED); -} - -int OBSBasic::GetTbarPosition() -{ - return tBar->value(); -} - -void OBSBasic::TogglePreviewProgramMode() -{ - SetPreviewProgramMode(!IsPreviewProgramMode()); -} - -static inline void ResetQuickTransitionText(QuickTransition *qt) -{ - qt->button->setText(MakeQuickTransitionText(qt)); -} - -QMenu *OBSBasic::CreatePerSceneTransitionMenu() -{ - OBSSource scene = GetCurrentSceneSource(); - QMenu *menu = new QMenu(QTStr("TransitionOverride")); - QAction *action; - - OBSDataAutoRelease data = obs_source_get_private_settings(scene); - - obs_data_set_default_int(data, "transition_duration", 300); - - const char *curTransition = obs_data_get_string(data, "transition"); - int curDuration = (int)obs_data_get_int(data, "transition_duration"); - - QSpinBox *duration = new QSpinBox(menu); - duration->setMinimum(50); - duration->setSuffix(" ms"); - duration->setMaximum(20000); - duration->setSingleStep(50); - duration->setValue(curDuration); - - auto setTransition = [this](QAction *action) { - int idx = action->property("transition_index").toInt(); - OBSSource scene = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(scene); - - if (idx == -1) { - obs_data_set_string(data, "transition", ""); - return; - } - - OBSSource tr = GetTransitionComboItem(ui->transitions, idx); - - if (tr) { - const char *name = obs_source_get_name(tr); - obs_data_set_string(data, "transition", name); - } - }; - - auto setDuration = [this](int duration) { - OBSSource scene = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(scene); - - obs_data_set_int(data, "transition_duration", duration); - }; - - connect(duration, (void(QSpinBox::*)(int)) & QSpinBox::valueChanged, setDuration); - - for (int i = -1; i < ui->transitions->count(); i++) { - const char *name = ""; - - if (i >= 0) { - OBSSource tr; - tr = GetTransitionComboItem(ui->transitions, i); - if (!tr) - continue; - name = obs_source_get_name(tr); - } - - bool match = (name && strcmp(name, curTransition) == 0); - - if (!name || !*name) - name = Str("None"); - - action = menu->addAction(QT_UTF8(name)); - action->setProperty("transition_index", i); - action->setCheckable(true); - action->setChecked(match); - - connect(action, &QAction::triggered, std::bind(setTransition, action)); - } - - QWidgetAction *durationAction = new QWidgetAction(menu); - durationAction->setDefaultWidget(duration); - - menu->addSeparator(); - menu->addAction(durationAction); - return menu; -} - -void OBSBasic::ShowTransitionProperties() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_transition(item, true); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::HideTransitionProperties() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_transition(item, false); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::PasteShowHideTransition(obs_sceneitem_t *item, bool show, obs_source_t *tr, int duration) -{ - int64_t sceneItemId = obs_sceneitem_get_id(item); - std::string sceneUUID = obs_source_get_uuid(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - auto undo_redo = [sceneUUID, sceneItemId, show](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(sceneUUID.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - obs_sceneitem_t *i = obs_scene_find_sceneitem_by_id(scene, sceneItemId); - if (i) { - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - obs_sceneitem_transition_load(i, dat, show); - } - }; - - OBSDataAutoRelease oldTransitionData = obs_sceneitem_transition_save(item, show); - - OBSSourceAutoRelease dup = obs_source_duplicate(tr, obs_source_get_name(tr), true); - obs_sceneitem_set_transition(item, show, dup); - obs_sceneitem_set_transition_duration(item, show, duration); - - OBSDataAutoRelease transitionData = obs_sceneitem_transition_save(item, show); - - std::string undo_data(obs_data_get_json(oldTransitionData)); - std::string redo_data(obs_data_get_json(transitionData)); - if (undo_data.compare(redo_data) == 0) - return; - - QString text = show ? QTStr("Undo.ShowTransition") : QTStr("Undo.HideTransition"); - const char *name = obs_source_get_name(obs_sceneitem_get_source(item)); - undo_s.add_action(text.arg(name), undo_redo, undo_redo, undo_data, redo_data); -} - -QMenu *OBSBasic::CreateVisibilityTransitionMenu(bool visible) -{ - OBSSceneItem si = GetCurrentSceneItem(); - - QMenu *menu = new QMenu(QTStr(visible ? "ShowTransition" : "HideTransition")); - QAction *action; - - OBSSource curTransition = obs_sceneitem_get_transition(si, visible); - const char *curId = curTransition ? obs_source_get_id(curTransition) : nullptr; - int curDuration = (int)obs_sceneitem_get_transition_duration(si, visible); - - if (curDuration <= 0) - curDuration = obs_frontend_get_transition_duration(); - - QSpinBox *duration = new QSpinBox(menu); - duration->setMinimum(50); - duration->setSuffix(" ms"); - duration->setMaximum(20000); - duration->setSingleStep(50); - duration->setValue(curDuration); - - auto setTransition = [this](QAction *action, bool visible) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - QString id = action->property("transition_id").toString(); - OBSSceneItem sceneItem = main->GetCurrentSceneItem(); - int64_t sceneItemId = obs_sceneitem_get_id(sceneItem); - std::string sceneUUID = obs_source_get_uuid(obs_scene_get_source(obs_sceneitem_get_scene(sceneItem))); - - auto undo_redo = [sceneUUID, sceneItemId, visible](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(sceneUUID.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - obs_sceneitem_t *i = obs_scene_find_sceneitem_by_id(scene, sceneItemId); - if (i) { - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - obs_sceneitem_transition_load(i, dat, visible); - } - }; - OBSDataAutoRelease oldTransitionData = obs_sceneitem_transition_save(sceneItem, visible); - if (id.isNull() || id.isEmpty()) { - obs_sceneitem_set_transition(sceneItem, visible, nullptr); - obs_sceneitem_set_transition_duration(sceneItem, visible, 0); - } else { - OBSSource tr = obs_sceneitem_get_transition(sceneItem, visible); - - if (!tr || strcmp(QT_TO_UTF8(id), obs_source_get_id(tr)) != 0) { - QString name = QT_UTF8(obs_source_get_name(obs_sceneitem_get_source(sceneItem))); - name += " "; - name += QTStr(visible ? "ShowTransition" : "HideTransition"); - tr = obs_source_create_private(QT_TO_UTF8(id), QT_TO_UTF8(name), nullptr); - obs_sceneitem_set_transition(sceneItem, visible, tr); - obs_source_release(tr); - - int duration = (int)obs_sceneitem_get_transition_duration(sceneItem, visible); - if (duration <= 0) { - duration = obs_frontend_get_transition_duration(); - obs_sceneitem_set_transition_duration(sceneItem, visible, duration); - } - } - if (obs_source_configurable(tr)) - CreatePropertiesWindow(tr); - } - OBSDataAutoRelease newTransitionData = obs_sceneitem_transition_save(sceneItem, visible); - std::string undo_data(obs_data_get_json(oldTransitionData)); - std::string redo_data(obs_data_get_json(newTransitionData)); - if (undo_data.compare(redo_data) != 0) - main->undo_s.add_action(QTStr(visible ? "Undo.ShowTransition" : "Undo.HideTransition") - .arg(obs_source_get_name(obs_sceneitem_get_source(sceneItem))), - undo_redo, undo_redo, undo_data, redo_data); - }; - auto setDuration = [visible](int duration) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - OBSSceneItem item = main->GetCurrentSceneItem(); - obs_sceneitem_set_transition_duration(item, visible, duration); - }; - connect(duration, (void(QSpinBox::*)(int)) & QSpinBox::valueChanged, setDuration); - - action = menu->addAction(QT_UTF8(Str("None"))); - action->setProperty("transition_id", QT_UTF8("")); - action->setCheckable(true); - action->setChecked(!curId); - connect(action, &QAction::triggered, std::bind(setTransition, action, visible)); - size_t idx = 0; - const char *id; - while (obs_enum_transition_types(idx++, &id)) { - const char *name = obs_source_get_display_name(id); - const bool match = id && curId && strcmp(id, curId) == 0; - action = menu->addAction(QT_UTF8(name)); - action->setProperty("transition_id", QT_UTF8(id)); - action->setCheckable(true); - action->setChecked(match); - connect(action, &QAction::triggered, std::bind(setTransition, action, visible)); - } - - QWidgetAction *durationAction = new QWidgetAction(menu); - durationAction->setDefaultWidget(duration); - - menu->addSeparator(); - menu->addAction(durationAction); - if (curId && obs_is_source_configurable(curId)) { - menu->addSeparator(); - menu->addAction(QTStr("Properties"), this, - visible ? &OBSBasic::ShowTransitionProperties : &OBSBasic::HideTransitionProperties); - } - - auto copyTransition = [this](QAction *, bool visible) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - OBSSceneItem item = main->GetCurrentSceneItem(); - obs_source_t *tr = obs_sceneitem_get_transition(item, visible); - int trDur = obs_sceneitem_get_transition_duration(item, visible); - main->copySourceTransition = obs_source_get_weak_source(tr); - main->copySourceTransitionDuration = trDur; - }; - menu->addSeparator(); - action = menu->addAction(QT_UTF8(Str("Copy"))); - action->setEnabled(curId != nullptr); - connect(action, &QAction::triggered, std::bind(copyTransition, action, visible)); - - auto pasteTransition = [this](QAction *, bool show) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - OBSSource tr = OBSGetStrongRef(main->copySourceTransition); - int trDuration = main->copySourceTransitionDuration; - if (!tr) - return; - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = main->ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - PasteShowHideTransition(item, show, tr, trDuration); - } - }; - - action = menu->addAction(QT_UTF8(Str("Paste"))); - action->setEnabled(!!OBSGetStrongRef(copySourceTransition)); - connect(action, &QAction::triggered, std::bind(pasteTransition, action, visible)); - return menu; -} - -QMenu *OBSBasic::CreateTransitionMenu(QWidget *parent, QuickTransition *qt) -{ - QMenu *menu = new QMenu(parent); - QAction *action; - OBSSource tr; - - if (qt) { - action = menu->addAction(QTStr("Remove")); - action->setProperty("id", qt->id); - connect(action, &QAction::triggered, this, &OBSBasic::QuickTransitionRemoveClicked); - - menu->addSeparator(); - } - - QSpinBox *duration = new QSpinBox(menu); - if (qt) - duration->setProperty("id", qt->id); - duration->setMinimum(50); - duration->setSuffix(" ms"); - duration->setMaximum(20000); - duration->setSingleStep(50); - duration->setValue(qt ? qt->duration : 300); - - if (qt) { - connect(duration, (void(QSpinBox::*)(int)) & QSpinBox::valueChanged, this, - &OBSBasic::QuickTransitionChangeDuration); - } - - tr = fadeTransition; - - action = menu->addAction(QTStr("FadeToBlack")); - action->setProperty("fadeToBlack", true); - - if (qt) { - action->setProperty("id", qt->id); - connect(action, &QAction::triggered, this, &OBSBasic::QuickTransitionChange); - } else { - action->setProperty("duration", QVariant::fromValue(duration)); - connect(action, &QAction::triggered, this, &OBSBasic::AddQuickTransition); - } - - for (int i = 0; i < ui->transitions->count(); i++) { - tr = GetTransitionComboItem(ui->transitions, i); - - if (!tr) - continue; - - action = menu->addAction(obs_source_get_name(tr)); - action->setProperty("transition_index", i); - - if (qt) { - action->setProperty("id", qt->id); - connect(action, &QAction::triggered, this, &OBSBasic::QuickTransitionChange); - } else { - action->setProperty("duration", QVariant::fromValue(duration)); - connect(action, &QAction::triggered, this, &OBSBasic::AddQuickTransition); - } - } - - QWidgetAction *durationAction = new QWidgetAction(menu); - durationAction->setDefaultWidget(duration); - - menu->addSeparator(); - menu->addAction(durationAction); - return menu; -} - -void OBSBasic::AddQuickTransitionId(int id) -{ - QuickTransition *qt = GetQuickTransition(id); - if (!qt) - return; - - /* --------------------------------- */ - - QPushButton *button = new MenuButton(); - button->setProperty("id", id); - - qt->button = button; - ResetQuickTransitionText(qt); - - /* --------------------------------- */ - - QMenu *buttonMenu = CreateTransitionMenu(button, qt); - - /* --------------------------------- */ - - button->setMenu(buttonMenu); - connect(button, &QAbstractButton::clicked, this, &OBSBasic::QuickTransitionClicked); - - QVBoxLayout *programLayout = reinterpret_cast(programOptions->layout()); - - int idx = 3; - for (;; idx++) { - QLayoutItem *item = programLayout->itemAt(idx); - if (!item) - break; - - QWidget *widget = item->widget(); - if (!widget || !widget->property("id").isValid()) - break; - } - - programLayout->insertWidget(idx, button); -} - -void OBSBasic::AddQuickTransition() -{ - int trIdx = sender()->property("transition_index").toInt(); - QSpinBox *duration = sender()->property("duration").value(); - bool fadeToBlack = sender()->property("fadeToBlack").value(); - OBSSource transition = fadeToBlack ? OBSSource(fadeTransition) : GetTransitionComboItem(ui->transitions, trIdx); - - if (!transition) - return; - - int id = quickTransitionIdCounter++; - - quickTransitions.emplace_back(transition, duration->value(), id, fadeToBlack); - AddQuickTransitionId(id); - - int idx = (int)quickTransitions.size() - 1; - AddQuickTransitionHotkey(&quickTransitions[idx]); -} - -void OBSBasic::ClearQuickTransitions() -{ - for (QuickTransition &qt : quickTransitions) - RemoveQuickTransitionHotkey(&qt); - quickTransitions.clear(); - - if (!programOptions) - return; - - QVBoxLayout *programLayout = reinterpret_cast(programOptions->layout()); - - for (int idx = 0;; idx++) { - QLayoutItem *item = programLayout->itemAt(idx); - if (!item) - break; - - QWidget *widget = item->widget(); - if (!widget) - continue; - - int id = widget->property("id").toInt(); - if (id != 0) { - delete widget; - idx--; - } - } -} - -void OBSBasic::QuickTransitionClicked() -{ - int id = sender()->property("id").toInt(); - TriggerQuickTransition(id); -} - -void OBSBasic::QuickTransitionChange() -{ - int id = sender()->property("id").toInt(); - int trIdx = sender()->property("transition_index").toInt(); - bool fadeToBlack = sender()->property("fadeToBlack").value(); - QuickTransition *qt = GetQuickTransition(id); - - if (qt) { - OBSSource tr = fadeToBlack ? OBSSource(fadeTransition) : GetTransitionComboItem(ui->transitions, trIdx); - if (tr) { - qt->source = tr; - qt->fadeToBlack = fadeToBlack; - ResetQuickTransitionText(qt); - } - } -} - -void OBSBasic::QuickTransitionChangeDuration(int value) -{ - int id = sender()->property("id").toInt(); - QuickTransition *qt = GetQuickTransition(id); - - if (qt) { - qt->duration = value; - ResetQuickTransitionText(qt); - } -} - -void OBSBasic::QuickTransitionRemoveClicked() -{ - int id = sender()->property("id").toInt(); - int idx = GetQuickTransitionIdx(id); - if (idx == -1) - return; - - QuickTransition &qt = quickTransitions[idx]; - - if (qt.button) - qt.button->deleteLater(); - - RemoveQuickTransitionHotkey(&qt); - quickTransitions.erase(quickTransitions.begin() + idx); -} - -void OBSBasic::ClearQuickTransitionWidgets() -{ - if (!IsPreviewProgramMode()) - return; - - QVBoxLayout *programLayout = reinterpret_cast(programOptions->layout()); - - for (int idx = 0;; idx++) { - QLayoutItem *item = programLayout->itemAt(idx); - if (!item) - break; - - QWidget *widget = item->widget(); - if (!widget) - continue; - - int id = widget->property("id").toInt(); - if (id != 0) { - delete widget; - idx--; - } - } -} - -void OBSBasic::RefreshQuickTransitions() -{ - if (!IsPreviewProgramMode()) - return; - - for (QuickTransition &qt : quickTransitions) - AddQuickTransitionId(qt.id); -} - -void OBSBasic::EnableTransitionWidgets(bool enable) -{ - ui->transitions->setEnabled(enable); - - if (!enable) { - ui->transitionProps->setEnabled(false); - } else { - bool configurable = obs_source_configurable(GetCurrentTransition()); - ui->transitionProps->setEnabled(configurable); - } - - if (!IsPreviewProgramMode()) - return; - - QVBoxLayout *programLayout = reinterpret_cast(programOptions->layout()); - - for (int idx = 0;; idx++) { - QLayoutItem *item = programLayout->itemAt(idx); - if (!item) - break; - - QPushButton *button = qobject_cast(item->widget()); - if (!button) - continue; - - button->setEnabled(enable); - } - - if (transitionButton) - transitionButton->setEnabled(enable); -} - -void OBSBasic::SetPreviewProgramMode(bool enabled) -{ - if (IsPreviewProgramMode() == enabled) - return; - - os_atomic_set_bool(&previewProgramMode, enabled); - emit PreviewProgramModeChanged(enabled); - - if (IsPreviewProgramMode()) { - if (!previewEnabled) - EnablePreviewDisplay(true); - - CreateProgramDisplay(); - CreateProgramOptions(); - - OBSScene curScene = GetCurrentScene(); - - OBSSceneAutoRelease dup; - if (sceneDuplicationMode) { - dup = obs_scene_duplicate(curScene, obs_source_get_name(obs_scene_get_source(curScene)), - editPropertiesMode ? OBS_SCENE_DUP_PRIVATE_COPY - : OBS_SCENE_DUP_PRIVATE_REFS); - } else { - dup = std::move(OBSScene(curScene)); - } - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *dup_source = obs_scene_get_source(dup); - obs_transition_set(transition, dup_source); - - if (curScene) { - obs_source_t *source = obs_scene_get_source(curScene); - obs_source_inc_showing(source); - lastScene = OBSGetWeakRef(source); - programScene = OBSGetWeakRef(source); - } - - RefreshQuickTransitions(); - - programLabel = new QLabel(QTStr("StudioMode.ProgramSceneLabel"), this); - programLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); - programLabel->setProperty("class", "label-preview-title"); - - programWidget = new QWidget(); - programLayout = new QVBoxLayout(); - - programLayout->setContentsMargins(0, 0, 0, 0); - programLayout->setSpacing(0); - - programLayout->addWidget(programLabel); - programLayout->addWidget(program); - - programWidget->setLayout(programLayout); - - ui->previewLayout->addWidget(programOptions); - ui->previewLayout->addWidget(programWidget); - ui->previewLayout->setAlignment(programOptions, Qt::AlignCenter); - - OnEvent(OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED); - - blog(LOG_INFO, "Switched to Preview/Program mode"); - blog(LOG_INFO, "-----------------------------" - "-------------------"); - } else { - OBSSource actualProgramScene = OBSGetStrongRef(programScene); - if (!actualProgramScene) - actualProgramScene = GetCurrentSceneSource(); - else - SetCurrentScene(actualProgramScene, true); - TransitionToScene(actualProgramScene, true); - - delete programOptions; - delete program; - delete programLabel; - delete programWidget; - - if (lastScene) { - OBSSource actualLastScene = OBSGetStrongRef(lastScene); - if (actualLastScene) - obs_source_dec_showing(actualLastScene); - lastScene = nullptr; - } - - programScene = nullptr; - swapScene = nullptr; - prevFTBSource = nullptr; - - for (QuickTransition &qt : quickTransitions) - qt.button = nullptr; - - if (!previewEnabled) - EnablePreviewDisplay(false); - - ui->transitions->setEnabled(true); - tBarActive = false; - - OnEvent(OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED); - - blog(LOG_INFO, "Switched to regular Preview mode"); - blog(LOG_INFO, "-----------------------------" - "-------------------"); - } - - ResetUI(); - UpdateTitleBar(); -} - -void OBSBasic::RenderProgram(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderProgram"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->programCX = int(window->programScale * float(ovi.base_width)); - window->programCY = int(window->programScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->programX, window->programY, window->programCX, window->programCY); - - obs_render_main_texture_src_color_only(); - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::ResizeProgram(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - - /* resize program panel to fix to the top section of the window */ - targetSize = GetPixelSize(program); - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, programX, programY, programScale); - - programX += float(PREVIEW_EDGE_SIZE); - programY += float(PREVIEW_EDGE_SIZE); -} - -obs_data_array_t *OBSBasic::SaveTransitions() -{ - obs_data_array_t *transitions = obs_data_array_create(); - - for (int i = 0; i < ui->transitions->count(); i++) { - OBSSource tr = ui->transitions->itemData(i).value(); - if (!tr || !obs_source_configurable(tr)) - continue; - - OBSDataAutoRelease sourceData = obs_data_create(); - OBSDataAutoRelease settings = obs_source_get_settings(tr); - - obs_data_set_string(sourceData, "name", obs_source_get_name(tr)); - obs_data_set_string(sourceData, "id", obs_obj_get_id(tr)); - obs_data_set_obj(sourceData, "settings", settings); - - obs_data_array_push_back(transitions, sourceData); - } - - for (const OBSDataAutoRelease &transition : safeModeTransitions) { - obs_data_array_push_back(transitions, transition); - } - - return transitions; -} - -void OBSBasic::LoadTransitions(obs_data_array_t *transitions, obs_load_source_cb cb, void *private_data) -{ - size_t count = obs_data_array_count(transitions); - - safeModeTransitions.clear(); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease item = obs_data_array_item(transitions, i); - const char *name = obs_data_get_string(item, "name"); - const char *id = obs_data_get_string(item, "id"); - OBSDataAutoRelease settings = obs_data_get_obj(item, "settings"); - - OBSSourceAutoRelease source = obs_source_create_private(id, name, settings); - if (!obs_obj_invalid(source)) { - InitTransition(source); - - ui->transitions->addItem(QT_UTF8(name), QVariant::fromValue(OBSSource(source))); - ui->transitions->setCurrentIndex(ui->transitions->count() - 1); - if (cb) - cb(private_data, source); - } else if (safe_mode || disable_3p_plugins) { - safeModeTransitions.push_back(std::move(item)); - } - } -} - -OBSSource OBSBasic::GetOverrideTransition(OBSSource source) -{ - if (!source) - return nullptr; - - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - const char *trOverrideName = obs_data_get_string(data, "transition"); - - OBSSource trOverride = nullptr; - - if (trOverrideName && *trOverrideName) - trOverride = FindTransition(trOverrideName); - - return trOverride; -} - -int OBSBasic::GetOverrideTransitionDuration(OBSSource source) -{ - if (!source) - return 300; - - OBSDataAutoRelease data = obs_source_get_private_settings(source); - obs_data_set_default_int(data, "transition_duration", 300); - - return (int)obs_data_get_int(data, "transition_duration"); -} - -void OBSBasic::UpdatePreviewProgramIndicators() -{ - bool labels = previewProgramMode ? config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioModeLabels") - : false; - - ui->previewLabel->setVisible(labels); - - if (programLabel) - programLabel->setVisible(labels); - - if (!labels) - return; - - QString preview = - QTStr("StudioMode.PreviewSceneName").arg(QT_UTF8(obs_source_get_name(GetCurrentSceneSource()))); - - QString program = QTStr("StudioMode.ProgramSceneName").arg(QT_UTF8(obs_source_get_name(GetProgramSource()))); - - if (ui->previewLabel->text() != preview) - ui->previewLabel->setText(preview); - - if (programLabel && programLabel->text() != program) - programLabel->setText(program); -} diff --git a/frontend/utility/QuickTransition.hpp b/frontend/utility/QuickTransition.hpp index 81bb7b478..a17f107f2 100644 --- a/frontend/utility/QuickTransition.hpp +++ b/frontend/utility/QuickTransition.hpp @@ -17,96 +17,13 @@ #pragma once -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include "window-main.hpp" -#include "window-basic-interaction.hpp" -#include "window-basic-vcam.hpp" -#include "window-basic-properties.hpp" -#include "window-basic-transform.hpp" -#include "window-basic-adv-audio.hpp" -#include "window-basic-filters.hpp" -#include "window-missing-files.hpp" -#include "window-projector.hpp" -#include "window-basic-about.hpp" -#ifdef YOUTUBE_ENABLED -#include "window-dock-youtube-app.hpp" -#endif -#include "auth-base.hpp" -#include "log-viewer.hpp" -#include "undo-stack-obs.hpp" -#include +#include -#include -#include -#include +using namespace std; -#include - -class QMessageBox; -class QListWidgetItem; -class VolControl; -class OBSBasicStats; -class OBSBasicVCamConfig; - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#define DESKTOP_AUDIO_1 Str("DesktopAudioDevice1") -#define DESKTOP_AUDIO_2 Str("DesktopAudioDevice2") -#define AUX_AUDIO_1 Str("AuxAudioDevice1") -#define AUX_AUDIO_2 Str("AuxAudioDevice2") -#define AUX_AUDIO_3 Str("AuxAudioDevice3") -#define AUX_AUDIO_4 Str("AuxAudioDevice4") - -#define SIMPLE_ENCODER_X264 "x264" -#define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" -#define SIMPLE_ENCODER_QSV "qsv" -#define SIMPLE_ENCODER_QSV_AV1 "qsv_av1" -#define SIMPLE_ENCODER_NVENC "nvenc" -#define SIMPLE_ENCODER_NVENC_AV1 "nvenc_av1" -#define SIMPLE_ENCODER_NVENC_HEVC "nvenc_hevc" -#define SIMPLE_ENCODER_AMD "amd" -#define SIMPLE_ENCODER_AMD_HEVC "amd_hevc" -#define SIMPLE_ENCODER_AMD_AV1 "amd_av1" -#define SIMPLE_ENCODER_APPLE_H264 "apple_h264" -#define SIMPLE_ENCODER_APPLE_HEVC "apple_hevc" - -#define PREVIEW_EDGE_SIZE 10 - -struct BasicOutputHandler; - -enum class QtDataRole { - OBSRef = Qt::UserRole, - OBSSignals, -}; - -struct SavedProjectorInfo { - ProjectorType type; - int monitor; - std::string geometry; - std::string name; - bool alwaysOnTop; - bool alwaysOnTopOverridden; -}; - -struct SourceCopyInfo { - OBSWeakSource weak_source; - bool visible; - obs_sceneitem_crop crop; - obs_transform_info transform; - obs_blending_method blend_method; - obs_blending_type blend_mode; -}; +class QPushButton; struct QuickTransition { QPushButton *button = nullptr; @@ -131,1252 +48,3 @@ private: static void SourceRenamed(void *param, calldata_t *data); std::shared_ptr renamedSignal; }; - -struct OBSProfile { - std::string name; - std::string directoryName; - std::filesystem::path path; - std::filesystem::path profileFile; -}; - -struct OBSSceneCollection { - std::string name; - std::string fileName; - std::filesystem::path collectionFile; -}; - -struct OBSPromptResult { - bool success; - std::string promptValue; - bool optionValue; -}; - -struct OBSPromptRequest { - std::string title; - std::string prompt; - std::string promptValue; - bool withOption; - std::string optionPrompt; - bool optionValue; -}; - -using OBSPromptCallback = std::function; - -using OBSProfileCache = std::map; -using OBSSceneCollectionCache = std::map; - -class ColorSelect : public QWidget { - -public: - explicit ColorSelect(QWidget *parent = 0); - -private: - std::unique_ptr ui; -}; - -class OBSBasic : public OBSMainWindow { - Q_OBJECT - Q_PROPERTY(QIcon imageIcon READ GetImageIcon WRITE SetImageIcon DESIGNABLE true) - Q_PROPERTY(QIcon colorIcon READ GetColorIcon WRITE SetColorIcon DESIGNABLE true) - Q_PROPERTY(QIcon slideshowIcon READ GetSlideshowIcon WRITE SetSlideshowIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioInputIcon READ GetAudioInputIcon WRITE SetAudioInputIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioOutputIcon READ GetAudioOutputIcon WRITE SetAudioOutputIcon DESIGNABLE true) - Q_PROPERTY(QIcon desktopCapIcon READ GetDesktopCapIcon WRITE SetDesktopCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon windowCapIcon READ GetWindowCapIcon WRITE SetWindowCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon gameCapIcon READ GetGameCapIcon WRITE SetGameCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon cameraIcon READ GetCameraIcon WRITE SetCameraIcon DESIGNABLE true) - Q_PROPERTY(QIcon textIcon READ GetTextIcon WRITE SetTextIcon DESIGNABLE true) - Q_PROPERTY(QIcon mediaIcon READ GetMediaIcon WRITE SetMediaIcon DESIGNABLE true) - Q_PROPERTY(QIcon browserIcon READ GetBrowserIcon WRITE SetBrowserIcon DESIGNABLE true) - Q_PROPERTY(QIcon groupIcon READ GetGroupIcon WRITE SetGroupIcon DESIGNABLE true) - Q_PROPERTY(QIcon sceneIcon READ GetSceneIcon WRITE SetSceneIcon DESIGNABLE true) - Q_PROPERTY(QIcon defaultIcon READ GetDefaultIcon WRITE SetDefaultIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioProcessOutputIcon READ GetAudioProcessOutputIcon WRITE SetAudioProcessOutputIcon - DESIGNABLE true) - - friend class OBSAbout; - friend class OBSBasicPreview; - friend class OBSBasicStatusBar; - friend class OBSBasicSourceSelect; - friend class OBSBasicTransform; - friend class OBSBasicSettings; - friend class Auth; - friend class AutoConfig; - friend class AutoConfigStreamPage; - friend class RecordButton; - friend class ControlsSplitButton; - friend class ExtraBrowsersModel; - friend class ExtraBrowsersDelegate; - friend class DeviceCaptureToolbar; - friend class OBSBasicSourceSelect; - friend class OBSYoutubeActions; - friend class OBSPermissions; - friend struct BasicOutputHandler; - friend struct OBSStudioAPI; - friend class ScreenshotObj; - - enum class MoveDir { Up, Down, Left, Right }; - - enum DropType { - DropType_RawText, - DropType_Text, - DropType_Image, - DropType_Media, - DropType_Html, - DropType_Url, - }; - - enum ContextBarSize { ContextBarSize_Minimized, ContextBarSize_Reduced, ContextBarSize_Normal }; - - enum class CenterType { - Scene, - Vertical, - Horizontal, - }; - -private: - obs_frontend_callbacks *api = nullptr; - - std::shared_ptr auth; - - std::vector volumes; - - std::vector signalHandlers; - - QList> oldExtraDocks; - QStringList oldExtraDockNames; - - OBSDataAutoRelease collectionModuleData; - std::vector safeModeTransitions; - - bool loaded = false; - long disableSaving = 1; - bool projectChanged = false; - bool previewEnabled = true; - ContextBarSize contextBarSize = ContextBarSize_Normal; - - std::deque clipboard; - OBSWeakSourceAutoRelease copyFiltersSource; - bool copyVisible = true; - obs_transform_info copiedTransformInfo; - obs_sceneitem_crop copiedCropInfo; - bool hasCopiedTransform = false; - OBSWeakSourceAutoRelease copySourceTransition; - int copySourceTransitionDuration; - - bool closing = false; - bool clearingFailed = false; - - QScopedPointer devicePropertiesThread; - QScopedPointer whatsNewInitThread; - QScopedPointer updateCheckThread; - QScopedPointer introCheckThread; - QScopedPointer logUploadThread; - - QPointer interaction; - QPointer properties; - QPointer transformWindow; - QPointer advAudioWindow; - QPointer filters; - QPointer statsDock; -#ifdef YOUTUBE_ENABLED - QPointer youtubeAppDock; - uint64_t lastYouTubeAppDockCreationTime = 0; -#endif - QPointer about; - QPointer missDialog; - QPointer logView; - - QPointer cpuUsageTimer; - QPointer diskFullTimer; - - QPointer nudge_timer; - bool recent_nudge = false; - - os_cpu_usage_info_t *cpuUsageInfo = nullptr; - - OBSService service; - std::unique_ptr outputHandler; - std::shared_future setupStreamingGuard; - bool streamingStopping = false; - bool recordingStopping = false; - bool replayBufferStopping = false; - - gs_vertbuffer_t *box = nullptr; - gs_vertbuffer_t *boxLeft = nullptr; - gs_vertbuffer_t *boxTop = nullptr; - gs_vertbuffer_t *boxRight = nullptr; - gs_vertbuffer_t *boxBottom = nullptr; - gs_vertbuffer_t *circle = nullptr; - - gs_vertbuffer_t *actionSafeMargin = nullptr; - gs_vertbuffer_t *graphicsSafeMargin = nullptr; - gs_vertbuffer_t *fourByThreeSafeMargin = nullptr; - gs_vertbuffer_t *leftLine = nullptr; - gs_vertbuffer_t *topLine = nullptr; - gs_vertbuffer_t *rightLine = nullptr; - - int previewX = 0, previewY = 0; - int previewCX = 0, previewCY = 0; - float previewScale = 0.0f; - - ConfigFile activeConfiguration; - - std::vector savedProjectorsArray; - std::vector projectors; - - QPointer stats; - QPointer remux; - QPointer extraBrowsers; - QPointer importer; - - QPointer transitionButton; - - bool vcamEnabled = false; - VCamConfig vcamConfig; - - QScopedPointer trayIcon; - QPointer sysTrayStream; - QPointer sysTrayRecord; - QPointer sysTrayReplayBuffer; - QPointer sysTrayVirtualCam; - QPointer showHide; - QPointer exit; - QPointer trayMenu; - QPointer previewProjector; - QPointer studioProgramProjector; - QPointer previewProjectorSource; - QPointer previewProjectorMain; - QPointer sceneProjectorMenu; - QPointer sourceProjector; - QPointer scaleFilteringMenu; - QPointer blendingMethodMenu; - QPointer blendingModeMenu; - QPointer colorMenu; - QPointer colorWidgetAction; - QPointer colorSelect; - QPointer deinterlaceMenu; - QPointer perSceneTransitionMenu; - QPointer shortcutFilter; - QPointer renameScene; - QPointer renameSource; - - QPointer programWidget; - QPointer programLayout; - QPointer programLabel; - - QScopedPointer patronJsonThread; - std::string patronJson; - - std::atomic currentScene = nullptr; - std::optional> lastOutputResolution; - std::optional> migrationBaseResolution; - bool usingAbsoluteCoordinates = false; - - void DisableRelativeCoordinates(bool disable); - - void OnEvent(enum obs_frontend_event event); - - void UpdateMultiviewProjectorMenu(); - - void DrawBackdrop(float cx, float cy); - - void SetupEncoders(); - - void CreateFirstRunSources(); - void CreateDefaultScene(bool firstStart); - - void UpdateVolumeControlsDecayRate(); - void UpdateVolumeControlsPeakMeterType(); - void ClearVolumeControls(); - - void UploadLog(const char *subdir, const char *file, const bool crash); - - void Save(const char *file); - void LoadData(obs_data_t *data, const char *file, bool remigrate = false); - void Load(const char *file, bool remigrate = false); - - void InitHotkeys(); - void CreateHotkeys(); - void ClearHotkeys(); - - bool InitService(); - - bool InitBasicConfigDefaults(); - void InitBasicConfigDefaults2(); - bool InitBasicConfig(); - - void InitOBSCallbacks(); - - void InitPrimitives(); - - void OnFirstLoad(); - - OBSSceneItem GetSceneItem(QListWidgetItem *item); - OBSSceneItem GetCurrentSceneItem(); - - bool QueryRemoveSource(obs_source_t *source); - - void TimedCheckForUpdates(); - void CheckForUpdates(bool manualUpdate); - - void GetFPSCommon(uint32_t &num, uint32_t &den) const; - void GetFPSInteger(uint32_t &num, uint32_t &den) const; - void GetFPSFraction(uint32_t &num, uint32_t &den) const; - void GetFPSNanoseconds(uint32_t &num, uint32_t &den) const; - void GetConfigFPS(uint32_t &num, uint32_t &den) const; - - void UpdatePreviewScalingMenu(); - - void LoadSceneListOrder(obs_data_array_t *array); - obs_data_array_t *SaveSceneListOrder(); - void ChangeSceneIndex(bool relative, int idx, int invalidIdx); - - void TempFileOutput(const char *path, int vBitrate, int aBitrate); - void TempStreamOutput(const char *url, const char *key, int vBitrate, int aBitrate); - - void CloseDialogs(); - void ClearSceneData(); - void ClearProjectors(); - - void Nudge(int dist, MoveDir dir); - - OBSProjector *OpenProjector(obs_source_t *source, int monitor, ProjectorType type); - - void GetAudioSourceFilters(); - void GetAudioSourceProperties(); - void VolControlContextMenu(); - void ToggleVolControlLayout(); - void ToggleMixerLayout(bool vertical); - - void LogScenes(); - void SaveProjectNow(); - - int GetTopSelectedSourceItem(); - - QModelIndexList GetAllSelectedSourceItems(); - - obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys, replayBufHotkeys, vcamHotkeys, - togglePreviewHotkeys, contextBarHotkeys; - obs_hotkey_id forceStreamingStopHotkey, splitFileHotkey, addChapterHotkey; - - void InitDefaultTransitions(); - void InitTransition(obs_source_t *transition); - obs_source_t *FindTransition(const char *name); - OBSSource GetCurrentTransition(); - obs_data_array_t *SaveTransitions(); - void LoadTransitions(obs_data_array_t *transitions, obs_load_source_cb cb, void *private_data); - - obs_source_t *fadeTransition; - obs_source_t *cutTransition; - - void CreateProgramDisplay(); - void CreateProgramOptions(); - void AddQuickTransitionId(int id); - void AddQuickTransition(); - void AddQuickTransitionHotkey(QuickTransition *qt); - void RemoveQuickTransitionHotkey(QuickTransition *qt); - void LoadQuickTransitions(obs_data_array_t *array); - obs_data_array_t *SaveQuickTransitions(); - void ClearQuickTransitionWidgets(); - void RefreshQuickTransitions(); - void DisableQuickTransitionWidgets(); - void EnableTransitionWidgets(bool enable); - void CreateDefaultQuickTransitions(); - - void PasteShowHideTransition(obs_sceneitem_t *item, bool show, obs_source_t *tr, int duration); - QMenu *CreatePerSceneTransitionMenu(); - QMenu *CreateVisibilityTransitionMenu(bool visible); - - QuickTransition *GetQuickTransition(int id); - int GetQuickTransitionIdx(int id); - QMenu *CreateTransitionMenu(QWidget *parent, QuickTransition *qt); - void ClearQuickTransitions(); - void QuickTransitionClicked(); - void QuickTransitionChange(); - void QuickTransitionChangeDuration(int value); - void QuickTransitionRemoveClicked(); - - void SetPreviewProgramMode(bool enabled); - void ResizeProgram(uint32_t cx, uint32_t cy); - void SetCurrentScene(obs_scene_t *scene, bool force = false); - static void RenderProgram(void *data, uint32_t cx, uint32_t cy); - - std::vector quickTransitions; - QPointer programOptions; - QPointer program; - OBSWeakSource lastScene; - OBSWeakSource swapScene; - OBSWeakSource programScene; - OBSWeakSource lastProgramScene; - bool editPropertiesMode = false; - bool sceneDuplicationMode = true; - bool swapScenesMode = true; - volatile bool previewProgramMode = false; - obs_hotkey_pair_id togglePreviewProgramHotkeys = 0; - obs_hotkey_id transitionHotkey = 0; - obs_hotkey_id statsHotkey = 0; - obs_hotkey_id screenshotHotkey = 0; - obs_hotkey_id sourceScreenshotHotkey = 0; - int quickTransitionIdCounter = 1; - bool overridingTransition = false; - - int programX = 0, programY = 0; - int programCX = 0, programCY = 0; - float programScale = 0.0f; - - int disableOutputsRef = 0; - - inline void OnActivate(bool force = false); - inline void OnDeactivate(); - - void AddDropSource(const char *file, DropType image); - void AddDropURL(const char *url, QString &name, obs_data_t *settings, const obs_video_info &ovi); - void ConfirmDropUrl(const QString &url); - void dragEnterEvent(QDragEnterEvent *event) override; - void dragLeaveEvent(QDragLeaveEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dropEvent(QDropEvent *event) override; - - bool sysTrayMinimizeToTray(); - - void EnumDialogs(); - - QList visDialogs; - QList modalDialogs; - QList visMsgBoxes; - - QList visDlgPositions; - - QByteArray startingDockLayout; - - obs_data_array_t *SaveProjectors(); - void LoadSavedProjectors(obs_data_array_t *savedProjectors); - - void MacBranchesFetched(const QString &branch, bool manualUpdate); - void ReceivedIntroJson(const QString &text); - void ShowWhatsNew(const QString &url); - - void UpdatePreviewProgramIndicators(); - - QStringList extraDockNames; - QList> extraDocks; - - QStringList extraCustomDockNames; - QList> extraCustomDocks; - -#ifdef BROWSER_AVAILABLE - QPointer extraBrowserMenuDocksSeparator; - - QList> extraBrowserDocks; - QStringList extraBrowserDockNames; - QStringList extraBrowserDockTargets; - - void ClearExtraBrowserDocks(); - void LoadExtraBrowserDocks(); - void SaveExtraBrowserDocks(); - void ManageExtraBrowserDocks(); - void AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate); -#endif - - QIcon imageIcon; - QIcon colorIcon; - QIcon slideshowIcon; - QIcon audioInputIcon; - QIcon audioOutputIcon; - QIcon desktopCapIcon; - QIcon windowCapIcon; - QIcon gameCapIcon; - QIcon cameraIcon; - QIcon textIcon; - QIcon mediaIcon; - QIcon browserIcon; - QIcon groupIcon; - QIcon sceneIcon; - QIcon defaultIcon; - QIcon audioProcessOutputIcon; - - QIcon GetImageIcon() const; - QIcon GetColorIcon() const; - QIcon GetSlideshowIcon() const; - QIcon GetAudioInputIcon() const; - QIcon GetAudioOutputIcon() const; - QIcon GetDesktopCapIcon() const; - QIcon GetWindowCapIcon() const; - QIcon GetGameCapIcon() const; - QIcon GetCameraIcon() const; - QIcon GetTextIcon() const; - QIcon GetMediaIcon() const; - QIcon GetBrowserIcon() const; - QIcon GetDefaultIcon() const; - QIcon GetAudioProcessOutputIcon() const; - - QSlider *tBar; - bool tBarActive = false; - - OBSSource GetOverrideTransition(OBSSource source); - int GetOverrideTransitionDuration(OBSSource source); - - void UpdateProjectorHideCursor(); - void UpdateProjectorAlwaysOnTop(bool top); - void ResetProjectors(); - - QPointer screenshotData; - - void MoveSceneItem(enum obs_order_movement movement, const QString &action_name); - - bool autoStartBroadcast = true; - bool autoStopBroadcast = true; - bool broadcastActive = false; - bool broadcastReady = false; - QPointer youtubeStreamCheckThread; -#ifdef YOUTUBE_ENABLED - void YoutubeStreamCheck(const std::string &key); - void ShowYouTubeAutoStartWarning(); - void YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now); -#endif - void BroadcastButtonClicked(); - void SetBroadcastFlowEnabled(bool enabled); - - void UpdatePreviewSafeAreas(); - bool drawSafeAreas = false; - - void CenterSelectedSceneItems(const CenterType ¢erType); - void ShowMissingFilesDialog(obs_missing_files_t *files); - - QColor selectionColor; - QColor cropColor; - QColor hoverColor; - - QColor GetCropColor() const; - QColor GetHoverColor() const; - - void UpdatePreviewSpacingHelpers(); - bool drawSpacingHelpers = true; - - float GetDevicePixelRatio(); - void SourceToolBarActionsSetEnabled(); - - std::string lastScreenshot; - std::string lastReplay; - - void UpdatePreviewOverflowSettings(); - void UpdatePreviewScrollbars(); - - bool streamingStarting = false; - - bool recordingStarted = false; - bool isRecordingPausable = false; - bool recordingPaused = false; - - bool restartingVCam = false; - -public slots: - void DeferSaveBegin(); - void DeferSaveEnd(); - - void DisplayStreamStartError(); - - void SetupBroadcast(); - - void StartStreaming(); - void StopStreaming(); - void ForceStopStreaming(); - - void StreamDelayStarting(int sec); - void StreamDelayStopping(int sec); - - void StreamingStart(); - void StreamStopping(); - void StreamingStop(int errorcode, QString last_error); - - void StartRecording(); - void StopRecording(); - - void RecordingStart(); - void RecordStopping(); - void RecordingStop(int code, QString last_error); - void RecordingFileChanged(QString lastRecordingPath); - - void ShowReplayBufferPauseWarning(); - void StartReplayBuffer(); - void StopReplayBuffer(); - - void ReplayBufferStart(); - void ReplayBufferSave(); - void ReplayBufferSaved(); - void ReplayBufferStopping(); - void ReplayBufferStop(int code); - - void StartVirtualCam(); - void StopVirtualCam(); - - void OnVirtualCamStart(); - void OnVirtualCamStop(int code); - - void SaveProjectDeferred(); - void SaveProject(); - - void SetTransition(OBSSource transition); - void OverrideTransition(OBSSource transition); - void TransitionToScene(OBSScene scene, bool force = false); - void TransitionToScene(OBSSource scene, bool force = false, bool quickTransition = false, int quickDuration = 0, - bool black = false, bool manual = false); - void SetCurrentScene(OBSSource scene, bool force = false); - - void UpdatePatronJson(const QString &text, const QString &error); - - void ShowContextBar(); - void HideContextBar(); - void PauseRecording(); - void UnpauseRecording(); - - void UpdateEditMenu(); - -private slots: - - void on_actionMainUndo_triggered(); - void on_actionMainRedo_triggered(); - - void AddSceneItem(OBSSceneItem item); - void AddScene(OBSSource source); - void RemoveScene(OBSSource source); - void RenameSources(OBSSource source, QString newName, QString prevName); - - void ActivateAudioSource(OBSSource source); - void DeactivateAudioSource(OBSSource source); - - void DuplicateSelectedScene(); - void RemoveSelectedScene(); - - void ToggleAlwaysOnTop(); - - void ReorderSources(OBSScene scene); - void RefreshSources(OBSScene scene); - - void ProcessHotkey(obs_hotkey_id id, bool pressed); - - void AddTransition(const char *id); - void RenameTransition(OBSSource transition); - void TransitionClicked(); - void TransitionStopped(); - void TransitionFullyStopped(); - void TriggerQuickTransition(int id); - - void SetDeinterlacingMode(); - void SetDeinterlacingOrder(); - - void SetScaleFilter(); - - void SetBlendingMethod(); - void SetBlendingMode(); - - void IconActivated(QSystemTrayIcon::ActivationReason reason); - void SetShowing(bool showing); - - void ToggleShowHide(); - - void HideAudioControl(); - void UnhideAllAudioControls(); - void ToggleHideMixer(); - - void MixerRenameSource(); - - void on_vMixerScrollArea_customContextMenuRequested(); - void on_hMixerScrollArea_customContextMenuRequested(); - - void on_actionCopySource_triggered(); - void on_actionPasteRef_triggered(); - void on_actionPasteDup_triggered(); - - void on_actionCopyFilters_triggered(); - void on_actionPasteFilters_triggered(); - void AudioMixerCopyFilters(); - void AudioMixerPasteFilters(); - void SourcePasteFilters(OBSSource source, OBSSource dstSource); - - void on_previewXScrollBar_valueChanged(int value); - void on_previewYScrollBar_valueChanged(int value); - - void PreviewScalingModeChanged(int value); - - void ColorChange(); - - SourceTreeItem *GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem); - - void on_actionShowAbout_triggered(); - - void EnablePreview(); - void DisablePreview(); - - void EnablePreviewProgram(); - void DisablePreviewProgram(); - - void SceneCopyFilters(); - void ScenePasteFilters(); - - void CheckDiskSpaceRemaining(); - void OpenSavedProjector(SavedProjectorInfo *info); - - void ResetStatsHotkey(); - - void SetImageIcon(const QIcon &icon); - void SetColorIcon(const QIcon &icon); - void SetSlideshowIcon(const QIcon &icon); - void SetAudioInputIcon(const QIcon &icon); - void SetAudioOutputIcon(const QIcon &icon); - void SetDesktopCapIcon(const QIcon &icon); - void SetWindowCapIcon(const QIcon &icon); - void SetGameCapIcon(const QIcon &icon); - void SetCameraIcon(const QIcon &icon); - void SetTextIcon(const QIcon &icon); - void SetMediaIcon(const QIcon &icon); - void SetBrowserIcon(const QIcon &icon); - void SetGroupIcon(const QIcon &icon); - void SetSceneIcon(const QIcon &icon); - void SetDefaultIcon(const QIcon &icon); - void SetAudioProcessOutputIcon(const QIcon &icon); - - void TBarChanged(int value); - void TBarReleased(); - - void LockVolumeControl(bool lock); - - void UpdateVirtualCamConfig(const VCamConfig &config); - void RestartVirtualCam(const VCamConfig &config); - void RestartingVirtualCam(); - -private: - /* OBS Callbacks */ - static void SceneReordered(void *data, calldata_t *params); - static void SceneRefreshed(void *data, calldata_t *params); - static void SceneItemAdded(void *data, calldata_t *params); - static void SourceCreated(void *data, calldata_t *params); - static void SourceRemoved(void *data, calldata_t *params); - static void SourceActivated(void *data, calldata_t *params); - static void SourceDeactivated(void *data, calldata_t *params); - static void SourceAudioActivated(void *data, calldata_t *params); - static void SourceAudioDeactivated(void *data, calldata_t *params); - static void SourceRenamed(void *data, calldata_t *params); - static void RenderMain(void *data, uint32_t cx, uint32_t cy); - - void ResizePreview(uint32_t cx, uint32_t cy); - - void AddSource(const char *id); - QMenu *CreateAddSourcePopupMenu(); - void AddSourcePopupMenu(const QPoint &pos); - void copyActionsDynamicProperties(); - - static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); - - void AutoRemux(QString input, bool no_show = false); - - void UpdateIsRecordingPausable(); - - bool IsFFmpegOutputToURL() const; - bool OutputPathValid(); - void OutputPathInvalidMessage(); - - bool LowDiskSpace(); - void DiskSpaceMessage(); - - OBSSource prevFTBSource = nullptr; - - float dpi = 1.0; - -public: - OBSSource GetProgramSource(); - OBSScene GetCurrentScene(); - - void SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n); - - inline OBSSource GetCurrentSceneSource() - { - OBSScene curScene = GetCurrentScene(); - return OBSSource(obs_scene_get_source(curScene)); - } - - obs_service_t *GetService(); - void SetService(obs_service_t *service); - - int GetTransitionDuration(); - int GetTbarPosition(); - - inline bool IsPreviewProgramMode() const { return os_atomic_load_bool(&previewProgramMode); } - - inline bool VCamEnabled() const { return vcamEnabled; } - - bool Active() const; - - void ResetUI(); - int ResetVideo(); - bool ResetAudio(); - - void ResetOutputs(); - - void RefreshVolumeColors(); - - void ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel); - - void NewProject(); - void LoadProject(); - - inline void GetDisplayRect(int &x, int &y, int &cx, int &cy) - { - x = previewX; - y = previewY; - cx = previewCX; - cy = previewCY; - } - - inline bool SavingDisabled() const { return disableSaving; } - - inline double GetCPUUsage() const { return os_cpu_usage_info_query(cpuUsageInfo); } - - void SaveService(); - bool LoadService(); - - inline Auth *GetAuth() { return auth.get(); } - - inline void EnableOutputs(bool enable) - { - if (enable) { - if (--disableOutputsRef < 0) - disableOutputsRef = 0; - } else { - disableOutputsRef++; - } - } - - QMenu *AddDeinterlacingMenu(QMenu *menu, obs_source_t *source); - QMenu *AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item); - void CreateSourcePopupMenu(int idx, bool preview); - - void UpdateTitleBar(); - - void SystemTrayInit(); - void SystemTray(bool firstStarted); - - void OpenSavedProjectors(); - - void CreateInteractionWindow(obs_source_t *source); - void CreatePropertiesWindow(obs_source_t *source); - void CreateFiltersWindow(obs_source_t *source); - void CreateEditTransformWindow(obs_sceneitem_t *item); - - QAction *AddDockWidget(QDockWidget *dock); - void AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser = false); - void RemoveDockWidget(const QString &name); - bool IsDockObjectNameUsed(const QString &name); - void AddCustomDockWidget(QDockWidget *dock); - - static OBSBasic *Get(); - - const char *GetCurrentOutputPath(); - - void DeleteProjector(OBSProjector *projector); - - static QList GetProjectorMenuMonitorsFormatted(); - template - static void AddProjectorMenuMonitors(QMenu *parent, Receiver *target, void (Receiver::*slot)(Args...)) - { - auto projectors = GetProjectorMenuMonitorsFormatted(); - for (int i = 0; i < projectors.size(); i++) { - QString str = projectors[i]; - QAction *action = parent->addAction(str, target, slot); - action->setProperty("monitor", i); - } - } - - QIcon GetSourceIcon(const char *id) const; - QIcon GetGroupIcon() const; - QIcon GetSceneIcon() const; - - OBSWeakSource copyFilter; - - void ShowStatusBarMessage(const QString &message); - - static OBSData BackupScene(obs_scene_t *scene, std::vector *sources = nullptr); - void CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data); - - static inline OBSData BackupScene(obs_source_t *scene_source, std::vector *sources = nullptr) - { - obs_scene_t *scene = obs_scene_from_source(scene_source); - return BackupScene(scene, sources); - } - - void CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array); - - void SetDisplayAffinity(QWindow *window); - - QColor GetSelectionColor() const; - inline bool Closing() { return closing; } - -protected: - virtual void closeEvent(QCloseEvent *event) override; - virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; - virtual void changeEvent(QEvent *event) override; - -private slots: - void on_actionFullscreenInterface_triggered(); - - void on_actionShow_Recordings_triggered(); - void on_actionRemux_triggered(); - void on_action_Settings_triggered(); - void on_actionShowMacPermissions_triggered(); - void on_actionShowMissingFiles_triggered(); - void on_actionAdvAudioProperties_triggered(); - void on_actionMixerToolbarAdvAudio_triggered(); - void on_actionMixerToolbarMenu_triggered(); - void on_actionShowLogs_triggered(); - void on_actionUploadCurrentLog_triggered(); - void on_actionUploadLastLog_triggered(); - void on_actionViewCurrentLog_triggered(); - void on_actionCheckForUpdates_triggered(); - void on_actionRepair_triggered(); - void on_actionShowWhatsNew_triggered(); - void on_actionRestartSafe_triggered(); - - void on_actionShowCrashLogs_triggered(); - void on_actionUploadLastCrashLog_triggered(); - - void on_actionEditTransform_triggered(); - void on_actionCopyTransform_triggered(); - void on_actionPasteTransform_triggered(); - void on_actionRotate90CW_triggered(); - void on_actionRotate90CCW_triggered(); - void on_actionRotate180_triggered(); - void on_actionFlipHorizontal_triggered(); - void on_actionFlipVertical_triggered(); - void on_actionFitToScreen_triggered(); - void on_actionStretchToScreen_triggered(); - void on_actionCenterToScreen_triggered(); - void on_actionVerticalCenter_triggered(); - void on_actionHorizontalCenter_triggered(); - void on_actionSceneFilters_triggered(); - - void on_OBSBasic_customContextMenuRequested(const QPoint &pos); - - void on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *prev); - void on_scenes_customContextMenuRequested(const QPoint &pos); - void GridActionClicked(); - void on_actionSceneListMode_triggered(); - void on_actionSceneGridMode_triggered(); - void on_actionAddScene_triggered(); - void on_actionRemoveScene_triggered(); - void on_actionSceneUp_triggered(); - void on_actionSceneDown_triggered(); - void on_sources_customContextMenuRequested(const QPoint &pos); - void on_scenes_itemDoubleClicked(QListWidgetItem *item); - void on_actionAddSource_triggered(); - void on_actionRemoveSource_triggered(); - void on_actionInteract_triggered(); - void on_actionSourceProperties_triggered(); - void on_actionSourceUp_triggered(); - void on_actionSourceDown_triggered(); - - void on_actionMoveUp_triggered(); - void on_actionMoveDown_triggered(); - void on_actionMoveToTop_triggered(); - void on_actionMoveToBottom_triggered(); - - void on_actionLockPreview_triggered(); - - void on_scalingMenu_aboutToShow(); - void on_actionScaleWindow_triggered(); - void on_actionScaleCanvas_triggered(); - void on_actionScaleOutput_triggered(); - - void Screenshot(OBSSource source_ = nullptr); - void ScreenshotSelectedSource(); - void ScreenshotProgram(); - void ScreenshotScene(); - - void on_actionHelpPortal_triggered(); - void on_actionWebsite_triggered(); - void on_actionDiscord_triggered(); - void on_actionReleaseNotes_triggered(); - - void on_preview_customContextMenuRequested(); - void ProgramViewContextMenuRequested(); - void on_previewDisabledWidget_customContextMenuRequested(); - - void on_actionShowSettingsFolder_triggered(); - void on_actionShowProfileFolder_triggered(); - - void on_actionAlwaysOnTop_triggered(); - - void on_toggleListboxToolbars_toggled(bool visible); - void on_toggleContextBar_toggled(bool visible); - void on_toggleStatusBar_toggled(bool visible); - void on_toggleSourceIcons_toggled(bool visible); - - void on_transitions_currentIndexChanged(int index); - void on_transitionAdd_clicked(); - void on_transitionRemove_clicked(); - void on_transitionProps_clicked(); - void on_transitionDuration_valueChanged(); - - void ShowTransitionProperties(); - void HideTransitionProperties(); - - // Source Context Buttons - void on_sourcePropertiesButton_clicked(); - void on_sourceFiltersButton_clicked(); - void on_sourceInteractButton_clicked(); - - void on_autoConfigure_triggered(); - void on_stats_triggered(); - - void on_resetUI_triggered(); - void on_resetDocks_triggered(bool force = false); - void on_lockDocks_toggled(bool lock); - void on_multiviewProjectorWindowed_triggered(); - void on_sideDocks_toggled(bool side); - - void logUploadFinished(const QString &text, const QString &error); - void crashUploadFinished(const QString &text, const QString &error); - void openLogDialog(const QString &text, const bool crash); - - void updateCheckFinished(); - - void MoveSceneToTop(); - void MoveSceneToBottom(); - - void EditSceneName(); - void EditSceneItemName(); - - void SceneNameEdited(QWidget *editor); - - void OpenSceneFilters(); - void OpenFilters(OBSSource source = nullptr); - void OpenProperties(OBSSource source = nullptr); - void OpenInteraction(OBSSource source = nullptr); - void OpenEditTransform(OBSSceneItem item = nullptr); - - void EnablePreviewDisplay(bool enable); - void TogglePreview(); - - void OpenStudioProgramProjector(); - void OpenPreviewProjector(); - void OpenSourceProjector(); - void OpenMultiviewProjector(); - void OpenSceneProjector(); - - void OpenStudioProgramWindow(); - void OpenPreviewWindow(); - void OpenSourceWindow(); - void OpenSceneWindow(); - - void StackedMixerAreaContextMenuRequested(); - - void ResizeOutputSizeOfSource(); - - void RepairOldExtraDockName(); - void RepairCustomExtraDockName(); - - /* Stream action (start/stop) slot */ - void StreamActionTriggered(); - - /* Record action (start/stop) slot */ - void RecordActionTriggered(); - - /* Record pause (pause/unpause) slot */ - void RecordPauseToggled(); - - /* Replay Buffer action (start/stop) slot */ - void ReplayBufferActionTriggered(); - - /* Virtual Cam action (start/stop) slots */ - void VirtualCamActionTriggered(); - - void OpenVirtualCamConfig(); - - /* Studio Mode toggle slot */ - void TogglePreviewProgramMode(); - -public slots: - void on_actionResetTransform_triggered(); - - bool StreamingActive(); - bool RecordingActive(); - bool ReplayBufferActive(); - bool VirtualCamActive(); - - void ClearContextBar(); - void UpdateContextBar(bool force = false); - void UpdateContextBarDeferred(bool force = false); - void UpdateContextBarVisibility(); - -signals: - /* Streaming signals */ - void StreamingPreparing(); - void StreamingStarting(bool broadcastAutoStart); - void StreamingStarted(bool withDelay = false); - void StreamingStopping(); - void StreamingStopped(bool withDelay = false); - - /* Broadcast Flow signals */ - void BroadcastFlowEnabled(bool enabled); - void BroadcastStreamReady(bool ready); - void BroadcastStreamActive(); - void BroadcastStreamStarted(bool autoStop); - - /* Recording signals */ - void RecordingStarted(bool pausable = false); - void RecordingPaused(); - void RecordingUnpaused(); - void RecordingStopping(); - void RecordingStopped(); - - /* Replay Buffer signals */ - void ReplayBufEnabled(bool enabled); - void ReplayBufStarted(); - void ReplayBufStopping(); - void ReplayBufStopped(); - - /* Virtual Camera signals */ - void VirtualCamEnabled(); - void VirtualCamStarted(); - void VirtualCamStopped(); - - /* Studio Mode signal */ - void PreviewProgramModeChanged(bool enabled); - void CanvasResized(uint32_t width, uint32_t height); - void OutputResized(uint32_t width, uint32_t height); - - /* Preview signals */ - void PreviewXScrollBarMoved(int value); - void PreviewYScrollBarMoved(int value); - -private: - std::unique_ptr ui; - - QPointer controlsDock; - -public: - /* `undo_s` needs to be declared after `ui` to prevent an uninitialized - * warning for `ui` while initializing `undo_s`. */ - undo_stack undo_s; - - explicit OBSBasic(QWidget *parent = 0); - virtual ~OBSBasic(); - - virtual void OBSInit() override; - - virtual config_t *Config() const override; - - virtual int GetProfilePath(char *path, size_t size, const char *file) const override; - - static void InitBrowserPanelSafeBlock(); -#ifdef YOUTUBE_ENABLED - void NewYouTubeAppDock(); - void DeleteYouTubeAppDock(); - YouTubeAppDock *GetYouTubeAppDock(); -#endif - // MARK: - Generic UI Helper Functions - OBSPromptResult PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback); - - // MARK: - OBS Profile Management -private: - OBSProfileCache profiles{}; - - void SetupNewProfile(const std::string &profileName, bool useWizard = false); - void SetupDuplicateProfile(const std::string &profileName); - void SetupRenameProfile(const std::string &profileName); - - const OBSProfile &CreateProfile(const std::string &profileName); - void RemoveProfile(OBSProfile profile); - - void ChangeProfile(); - - void RefreshProfileCache(); - - void RefreshProfiles(bool refreshCache = false); - - void ActivateProfile(const OBSProfile &profile, bool reset = false); - void UpdateProfileEncoders(); - std::vector GetRestartRequirements(const ConfigFile &config) const; - void ResetProfileData(); - void CheckForSimpleModeX264Fallback(); - -public: - inline const OBSProfileCache &GetProfileCache() const noexcept { return profiles; }; - - const OBSProfile &GetCurrentProfile() const; - - std::optional GetProfileByName(const std::string &profileName) const; - std::optional GetProfileByDirectoryName(const std::string &directoryName) const; - -private slots: - void on_actionNewProfile_triggered(); - void on_actionDupProfile_triggered(); - void on_actionRenameProfile_triggered(); - void on_actionRemoveProfile_triggered(bool skipConfirmation = false); - void on_actionImportProfile_triggered(); - void on_actionExportProfile_triggered(); - -public slots: - bool CreateNewProfile(const QString &name); - bool CreateDuplicateProfile(const QString &name); - void DeleteProfile(const QString &profileName); - - // MARK: - OBS Scene Collection Management -private: - OBSSceneCollectionCache collections{}; - - void SetupNewSceneCollection(const std::string &collectionName); - void SetupDuplicateSceneCollection(const std::string &collectionName); - void SetupRenameSceneCollection(const std::string &collectionName); - - const OBSSceneCollection &CreateSceneCollection(const std::string &collectionName); - void RemoveSceneCollection(OBSSceneCollection collection); - - bool CreateDuplicateSceneCollection(const QString &name); - void DeleteSceneCollection(const QString &name); - void ChangeSceneCollection(); - - void RefreshSceneCollectionCache(); - - void RefreshSceneCollections(bool refreshCache = false); - void ActivateSceneCollection(const OBSSceneCollection &collection); - -public: - inline const OBSSceneCollectionCache &GetSceneCollectionCache() const noexcept { return collections; }; - - const OBSSceneCollection &GetCurrentSceneCollection() const; - - std::optional GetSceneCollectionByName(const std::string &collectionName) const; - std::optional GetSceneCollectionByFileName(const std::string &fileName) const; - -private slots: - void on_actionNewSceneCollection_triggered(); - void on_actionDupSceneCollection_triggered(); - void on_actionRenameSceneCollection_triggered(); - void on_actionRemoveSceneCollection_triggered(bool skipConfirmation = false); - void on_actionImportSceneCollection_triggered(); - void on_actionExportSceneCollection_triggered(); - void on_actionRemigrateSceneCollection_triggered(); - -public slots: - bool CreateNewSceneCollection(const QString &name); -}; - -extern bool cef_js_avail; - -class SceneRenameDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - SceneRenameDelegate(QObject *parent); - virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override; - -protected: - virtual bool eventFilter(QObject *editor, QEvent *event) override; -}; diff --git a/frontend/utility/SceneRenameDelegate.cpp b/frontend/utility/SceneRenameDelegate.cpp index 47cac8dcc..e505c5205 100644 --- a/frontend/utility/SceneRenameDelegate.cpp +++ b/frontend/utility/SceneRenameDelegate.cpp @@ -16,9714 +16,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "SceneRenameDelegate.hpp" -#include -#include -#include -#include -#include +#include +#include -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} +#include "moc_SceneRenameDelegate.cpp" SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} @@ -9754,450 +53,3 @@ bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) return QStyledItemDelegate::eventFilter(editor, event); } - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/utility/SceneRenameDelegate.hpp b/frontend/utility/SceneRenameDelegate.hpp index 81bb7b478..d5212f32e 100644 --- a/frontend/utility/SceneRenameDelegate.hpp +++ b/frontend/utility/SceneRenameDelegate.hpp @@ -17,1358 +17,7 @@ #pragma once -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include "window-main.hpp" -#include "window-basic-interaction.hpp" -#include "window-basic-vcam.hpp" -#include "window-basic-properties.hpp" -#include "window-basic-transform.hpp" -#include "window-basic-adv-audio.hpp" -#include "window-basic-filters.hpp" -#include "window-missing-files.hpp" -#include "window-projector.hpp" -#include "window-basic-about.hpp" -#ifdef YOUTUBE_ENABLED -#include "window-dock-youtube-app.hpp" -#endif -#include "auth-base.hpp" -#include "log-viewer.hpp" -#include "undo-stack-obs.hpp" - -#include - -#include -#include -#include - -#include - -class QMessageBox; -class QListWidgetItem; -class VolControl; -class OBSBasicStats; -class OBSBasicVCamConfig; - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#define DESKTOP_AUDIO_1 Str("DesktopAudioDevice1") -#define DESKTOP_AUDIO_2 Str("DesktopAudioDevice2") -#define AUX_AUDIO_1 Str("AuxAudioDevice1") -#define AUX_AUDIO_2 Str("AuxAudioDevice2") -#define AUX_AUDIO_3 Str("AuxAudioDevice3") -#define AUX_AUDIO_4 Str("AuxAudioDevice4") - -#define SIMPLE_ENCODER_X264 "x264" -#define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" -#define SIMPLE_ENCODER_QSV "qsv" -#define SIMPLE_ENCODER_QSV_AV1 "qsv_av1" -#define SIMPLE_ENCODER_NVENC "nvenc" -#define SIMPLE_ENCODER_NVENC_AV1 "nvenc_av1" -#define SIMPLE_ENCODER_NVENC_HEVC "nvenc_hevc" -#define SIMPLE_ENCODER_AMD "amd" -#define SIMPLE_ENCODER_AMD_HEVC "amd_hevc" -#define SIMPLE_ENCODER_AMD_AV1 "amd_av1" -#define SIMPLE_ENCODER_APPLE_H264 "apple_h264" -#define SIMPLE_ENCODER_APPLE_HEVC "apple_hevc" - -#define PREVIEW_EDGE_SIZE 10 - -struct BasicOutputHandler; - -enum class QtDataRole { - OBSRef = Qt::UserRole, - OBSSignals, -}; - -struct SavedProjectorInfo { - ProjectorType type; - int monitor; - std::string geometry; - std::string name; - bool alwaysOnTop; - bool alwaysOnTopOverridden; -}; - -struct SourceCopyInfo { - OBSWeakSource weak_source; - bool visible; - obs_sceneitem_crop crop; - obs_transform_info transform; - obs_blending_method blend_method; - obs_blending_type blend_mode; -}; - -struct QuickTransition { - QPushButton *button = nullptr; - OBSSource source; - obs_hotkey_id hotkey = OBS_INVALID_HOTKEY_ID; - int duration = 0; - int id = 0; - bool fadeToBlack = false; - - inline QuickTransition() {} - inline QuickTransition(OBSSource source_, int duration_, int id_, bool fadeToBlack_ = false) - : source(source_), - duration(duration_), - id(id_), - fadeToBlack(fadeToBlack_), - renamedSignal(std::make_shared(obs_source_get_signal_handler(source), "rename", - SourceRenamed, this)) - { - } - -private: - static void SourceRenamed(void *param, calldata_t *data); - std::shared_ptr renamedSignal; -}; - -struct OBSProfile { - std::string name; - std::string directoryName; - std::filesystem::path path; - std::filesystem::path profileFile; -}; - -struct OBSSceneCollection { - std::string name; - std::string fileName; - std::filesystem::path collectionFile; -}; - -struct OBSPromptResult { - bool success; - std::string promptValue; - bool optionValue; -}; - -struct OBSPromptRequest { - std::string title; - std::string prompt; - std::string promptValue; - bool withOption; - std::string optionPrompt; - bool optionValue; -}; - -using OBSPromptCallback = std::function; - -using OBSProfileCache = std::map; -using OBSSceneCollectionCache = std::map; - -class ColorSelect : public QWidget { - -public: - explicit ColorSelect(QWidget *parent = 0); - -private: - std::unique_ptr ui; -}; - -class OBSBasic : public OBSMainWindow { - Q_OBJECT - Q_PROPERTY(QIcon imageIcon READ GetImageIcon WRITE SetImageIcon DESIGNABLE true) - Q_PROPERTY(QIcon colorIcon READ GetColorIcon WRITE SetColorIcon DESIGNABLE true) - Q_PROPERTY(QIcon slideshowIcon READ GetSlideshowIcon WRITE SetSlideshowIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioInputIcon READ GetAudioInputIcon WRITE SetAudioInputIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioOutputIcon READ GetAudioOutputIcon WRITE SetAudioOutputIcon DESIGNABLE true) - Q_PROPERTY(QIcon desktopCapIcon READ GetDesktopCapIcon WRITE SetDesktopCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon windowCapIcon READ GetWindowCapIcon WRITE SetWindowCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon gameCapIcon READ GetGameCapIcon WRITE SetGameCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon cameraIcon READ GetCameraIcon WRITE SetCameraIcon DESIGNABLE true) - Q_PROPERTY(QIcon textIcon READ GetTextIcon WRITE SetTextIcon DESIGNABLE true) - Q_PROPERTY(QIcon mediaIcon READ GetMediaIcon WRITE SetMediaIcon DESIGNABLE true) - Q_PROPERTY(QIcon browserIcon READ GetBrowserIcon WRITE SetBrowserIcon DESIGNABLE true) - Q_PROPERTY(QIcon groupIcon READ GetGroupIcon WRITE SetGroupIcon DESIGNABLE true) - Q_PROPERTY(QIcon sceneIcon READ GetSceneIcon WRITE SetSceneIcon DESIGNABLE true) - Q_PROPERTY(QIcon defaultIcon READ GetDefaultIcon WRITE SetDefaultIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioProcessOutputIcon READ GetAudioProcessOutputIcon WRITE SetAudioProcessOutputIcon - DESIGNABLE true) - - friend class OBSAbout; - friend class OBSBasicPreview; - friend class OBSBasicStatusBar; - friend class OBSBasicSourceSelect; - friend class OBSBasicTransform; - friend class OBSBasicSettings; - friend class Auth; - friend class AutoConfig; - friend class AutoConfigStreamPage; - friend class RecordButton; - friend class ControlsSplitButton; - friend class ExtraBrowsersModel; - friend class ExtraBrowsersDelegate; - friend class DeviceCaptureToolbar; - friend class OBSBasicSourceSelect; - friend class OBSYoutubeActions; - friend class OBSPermissions; - friend struct BasicOutputHandler; - friend struct OBSStudioAPI; - friend class ScreenshotObj; - - enum class MoveDir { Up, Down, Left, Right }; - - enum DropType { - DropType_RawText, - DropType_Text, - DropType_Image, - DropType_Media, - DropType_Html, - DropType_Url, - }; - - enum ContextBarSize { ContextBarSize_Minimized, ContextBarSize_Reduced, ContextBarSize_Normal }; - - enum class CenterType { - Scene, - Vertical, - Horizontal, - }; - -private: - obs_frontend_callbacks *api = nullptr; - - std::shared_ptr auth; - - std::vector volumes; - - std::vector signalHandlers; - - QList> oldExtraDocks; - QStringList oldExtraDockNames; - - OBSDataAutoRelease collectionModuleData; - std::vector safeModeTransitions; - - bool loaded = false; - long disableSaving = 1; - bool projectChanged = false; - bool previewEnabled = true; - ContextBarSize contextBarSize = ContextBarSize_Normal; - - std::deque clipboard; - OBSWeakSourceAutoRelease copyFiltersSource; - bool copyVisible = true; - obs_transform_info copiedTransformInfo; - obs_sceneitem_crop copiedCropInfo; - bool hasCopiedTransform = false; - OBSWeakSourceAutoRelease copySourceTransition; - int copySourceTransitionDuration; - - bool closing = false; - bool clearingFailed = false; - - QScopedPointer devicePropertiesThread; - QScopedPointer whatsNewInitThread; - QScopedPointer updateCheckThread; - QScopedPointer introCheckThread; - QScopedPointer logUploadThread; - - QPointer interaction; - QPointer properties; - QPointer transformWindow; - QPointer advAudioWindow; - QPointer filters; - QPointer statsDock; -#ifdef YOUTUBE_ENABLED - QPointer youtubeAppDock; - uint64_t lastYouTubeAppDockCreationTime = 0; -#endif - QPointer about; - QPointer missDialog; - QPointer logView; - - QPointer cpuUsageTimer; - QPointer diskFullTimer; - - QPointer nudge_timer; - bool recent_nudge = false; - - os_cpu_usage_info_t *cpuUsageInfo = nullptr; - - OBSService service; - std::unique_ptr outputHandler; - std::shared_future setupStreamingGuard; - bool streamingStopping = false; - bool recordingStopping = false; - bool replayBufferStopping = false; - - gs_vertbuffer_t *box = nullptr; - gs_vertbuffer_t *boxLeft = nullptr; - gs_vertbuffer_t *boxTop = nullptr; - gs_vertbuffer_t *boxRight = nullptr; - gs_vertbuffer_t *boxBottom = nullptr; - gs_vertbuffer_t *circle = nullptr; - - gs_vertbuffer_t *actionSafeMargin = nullptr; - gs_vertbuffer_t *graphicsSafeMargin = nullptr; - gs_vertbuffer_t *fourByThreeSafeMargin = nullptr; - gs_vertbuffer_t *leftLine = nullptr; - gs_vertbuffer_t *topLine = nullptr; - gs_vertbuffer_t *rightLine = nullptr; - - int previewX = 0, previewY = 0; - int previewCX = 0, previewCY = 0; - float previewScale = 0.0f; - - ConfigFile activeConfiguration; - - std::vector savedProjectorsArray; - std::vector projectors; - - QPointer stats; - QPointer remux; - QPointer extraBrowsers; - QPointer importer; - - QPointer transitionButton; - - bool vcamEnabled = false; - VCamConfig vcamConfig; - - QScopedPointer trayIcon; - QPointer sysTrayStream; - QPointer sysTrayRecord; - QPointer sysTrayReplayBuffer; - QPointer sysTrayVirtualCam; - QPointer showHide; - QPointer exit; - QPointer trayMenu; - QPointer previewProjector; - QPointer studioProgramProjector; - QPointer previewProjectorSource; - QPointer previewProjectorMain; - QPointer sceneProjectorMenu; - QPointer sourceProjector; - QPointer scaleFilteringMenu; - QPointer blendingMethodMenu; - QPointer blendingModeMenu; - QPointer colorMenu; - QPointer colorWidgetAction; - QPointer colorSelect; - QPointer deinterlaceMenu; - QPointer perSceneTransitionMenu; - QPointer shortcutFilter; - QPointer renameScene; - QPointer renameSource; - - QPointer programWidget; - QPointer programLayout; - QPointer programLabel; - - QScopedPointer patronJsonThread; - std::string patronJson; - - std::atomic currentScene = nullptr; - std::optional> lastOutputResolution; - std::optional> migrationBaseResolution; - bool usingAbsoluteCoordinates = false; - - void DisableRelativeCoordinates(bool disable); - - void OnEvent(enum obs_frontend_event event); - - void UpdateMultiviewProjectorMenu(); - - void DrawBackdrop(float cx, float cy); - - void SetupEncoders(); - - void CreateFirstRunSources(); - void CreateDefaultScene(bool firstStart); - - void UpdateVolumeControlsDecayRate(); - void UpdateVolumeControlsPeakMeterType(); - void ClearVolumeControls(); - - void UploadLog(const char *subdir, const char *file, const bool crash); - - void Save(const char *file); - void LoadData(obs_data_t *data, const char *file, bool remigrate = false); - void Load(const char *file, bool remigrate = false); - - void InitHotkeys(); - void CreateHotkeys(); - void ClearHotkeys(); - - bool InitService(); - - bool InitBasicConfigDefaults(); - void InitBasicConfigDefaults2(); - bool InitBasicConfig(); - - void InitOBSCallbacks(); - - void InitPrimitives(); - - void OnFirstLoad(); - - OBSSceneItem GetSceneItem(QListWidgetItem *item); - OBSSceneItem GetCurrentSceneItem(); - - bool QueryRemoveSource(obs_source_t *source); - - void TimedCheckForUpdates(); - void CheckForUpdates(bool manualUpdate); - - void GetFPSCommon(uint32_t &num, uint32_t &den) const; - void GetFPSInteger(uint32_t &num, uint32_t &den) const; - void GetFPSFraction(uint32_t &num, uint32_t &den) const; - void GetFPSNanoseconds(uint32_t &num, uint32_t &den) const; - void GetConfigFPS(uint32_t &num, uint32_t &den) const; - - void UpdatePreviewScalingMenu(); - - void LoadSceneListOrder(obs_data_array_t *array); - obs_data_array_t *SaveSceneListOrder(); - void ChangeSceneIndex(bool relative, int idx, int invalidIdx); - - void TempFileOutput(const char *path, int vBitrate, int aBitrate); - void TempStreamOutput(const char *url, const char *key, int vBitrate, int aBitrate); - - void CloseDialogs(); - void ClearSceneData(); - void ClearProjectors(); - - void Nudge(int dist, MoveDir dir); - - OBSProjector *OpenProjector(obs_source_t *source, int monitor, ProjectorType type); - - void GetAudioSourceFilters(); - void GetAudioSourceProperties(); - void VolControlContextMenu(); - void ToggleVolControlLayout(); - void ToggleMixerLayout(bool vertical); - - void LogScenes(); - void SaveProjectNow(); - - int GetTopSelectedSourceItem(); - - QModelIndexList GetAllSelectedSourceItems(); - - obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys, replayBufHotkeys, vcamHotkeys, - togglePreviewHotkeys, contextBarHotkeys; - obs_hotkey_id forceStreamingStopHotkey, splitFileHotkey, addChapterHotkey; - - void InitDefaultTransitions(); - void InitTransition(obs_source_t *transition); - obs_source_t *FindTransition(const char *name); - OBSSource GetCurrentTransition(); - obs_data_array_t *SaveTransitions(); - void LoadTransitions(obs_data_array_t *transitions, obs_load_source_cb cb, void *private_data); - - obs_source_t *fadeTransition; - obs_source_t *cutTransition; - - void CreateProgramDisplay(); - void CreateProgramOptions(); - void AddQuickTransitionId(int id); - void AddQuickTransition(); - void AddQuickTransitionHotkey(QuickTransition *qt); - void RemoveQuickTransitionHotkey(QuickTransition *qt); - void LoadQuickTransitions(obs_data_array_t *array); - obs_data_array_t *SaveQuickTransitions(); - void ClearQuickTransitionWidgets(); - void RefreshQuickTransitions(); - void DisableQuickTransitionWidgets(); - void EnableTransitionWidgets(bool enable); - void CreateDefaultQuickTransitions(); - - void PasteShowHideTransition(obs_sceneitem_t *item, bool show, obs_source_t *tr, int duration); - QMenu *CreatePerSceneTransitionMenu(); - QMenu *CreateVisibilityTransitionMenu(bool visible); - - QuickTransition *GetQuickTransition(int id); - int GetQuickTransitionIdx(int id); - QMenu *CreateTransitionMenu(QWidget *parent, QuickTransition *qt); - void ClearQuickTransitions(); - void QuickTransitionClicked(); - void QuickTransitionChange(); - void QuickTransitionChangeDuration(int value); - void QuickTransitionRemoveClicked(); - - void SetPreviewProgramMode(bool enabled); - void ResizeProgram(uint32_t cx, uint32_t cy); - void SetCurrentScene(obs_scene_t *scene, bool force = false); - static void RenderProgram(void *data, uint32_t cx, uint32_t cy); - - std::vector quickTransitions; - QPointer programOptions; - QPointer program; - OBSWeakSource lastScene; - OBSWeakSource swapScene; - OBSWeakSource programScene; - OBSWeakSource lastProgramScene; - bool editPropertiesMode = false; - bool sceneDuplicationMode = true; - bool swapScenesMode = true; - volatile bool previewProgramMode = false; - obs_hotkey_pair_id togglePreviewProgramHotkeys = 0; - obs_hotkey_id transitionHotkey = 0; - obs_hotkey_id statsHotkey = 0; - obs_hotkey_id screenshotHotkey = 0; - obs_hotkey_id sourceScreenshotHotkey = 0; - int quickTransitionIdCounter = 1; - bool overridingTransition = false; - - int programX = 0, programY = 0; - int programCX = 0, programCY = 0; - float programScale = 0.0f; - - int disableOutputsRef = 0; - - inline void OnActivate(bool force = false); - inline void OnDeactivate(); - - void AddDropSource(const char *file, DropType image); - void AddDropURL(const char *url, QString &name, obs_data_t *settings, const obs_video_info &ovi); - void ConfirmDropUrl(const QString &url); - void dragEnterEvent(QDragEnterEvent *event) override; - void dragLeaveEvent(QDragLeaveEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dropEvent(QDropEvent *event) override; - - bool sysTrayMinimizeToTray(); - - void EnumDialogs(); - - QList visDialogs; - QList modalDialogs; - QList visMsgBoxes; - - QList visDlgPositions; - - QByteArray startingDockLayout; - - obs_data_array_t *SaveProjectors(); - void LoadSavedProjectors(obs_data_array_t *savedProjectors); - - void MacBranchesFetched(const QString &branch, bool manualUpdate); - void ReceivedIntroJson(const QString &text); - void ShowWhatsNew(const QString &url); - - void UpdatePreviewProgramIndicators(); - - QStringList extraDockNames; - QList> extraDocks; - - QStringList extraCustomDockNames; - QList> extraCustomDocks; - -#ifdef BROWSER_AVAILABLE - QPointer extraBrowserMenuDocksSeparator; - - QList> extraBrowserDocks; - QStringList extraBrowserDockNames; - QStringList extraBrowserDockTargets; - - void ClearExtraBrowserDocks(); - void LoadExtraBrowserDocks(); - void SaveExtraBrowserDocks(); - void ManageExtraBrowserDocks(); - void AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate); -#endif - - QIcon imageIcon; - QIcon colorIcon; - QIcon slideshowIcon; - QIcon audioInputIcon; - QIcon audioOutputIcon; - QIcon desktopCapIcon; - QIcon windowCapIcon; - QIcon gameCapIcon; - QIcon cameraIcon; - QIcon textIcon; - QIcon mediaIcon; - QIcon browserIcon; - QIcon groupIcon; - QIcon sceneIcon; - QIcon defaultIcon; - QIcon audioProcessOutputIcon; - - QIcon GetImageIcon() const; - QIcon GetColorIcon() const; - QIcon GetSlideshowIcon() const; - QIcon GetAudioInputIcon() const; - QIcon GetAudioOutputIcon() const; - QIcon GetDesktopCapIcon() const; - QIcon GetWindowCapIcon() const; - QIcon GetGameCapIcon() const; - QIcon GetCameraIcon() const; - QIcon GetTextIcon() const; - QIcon GetMediaIcon() const; - QIcon GetBrowserIcon() const; - QIcon GetDefaultIcon() const; - QIcon GetAudioProcessOutputIcon() const; - - QSlider *tBar; - bool tBarActive = false; - - OBSSource GetOverrideTransition(OBSSource source); - int GetOverrideTransitionDuration(OBSSource source); - - void UpdateProjectorHideCursor(); - void UpdateProjectorAlwaysOnTop(bool top); - void ResetProjectors(); - - QPointer screenshotData; - - void MoveSceneItem(enum obs_order_movement movement, const QString &action_name); - - bool autoStartBroadcast = true; - bool autoStopBroadcast = true; - bool broadcastActive = false; - bool broadcastReady = false; - QPointer youtubeStreamCheckThread; -#ifdef YOUTUBE_ENABLED - void YoutubeStreamCheck(const std::string &key); - void ShowYouTubeAutoStartWarning(); - void YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now); -#endif - void BroadcastButtonClicked(); - void SetBroadcastFlowEnabled(bool enabled); - - void UpdatePreviewSafeAreas(); - bool drawSafeAreas = false; - - void CenterSelectedSceneItems(const CenterType ¢erType); - void ShowMissingFilesDialog(obs_missing_files_t *files); - - QColor selectionColor; - QColor cropColor; - QColor hoverColor; - - QColor GetCropColor() const; - QColor GetHoverColor() const; - - void UpdatePreviewSpacingHelpers(); - bool drawSpacingHelpers = true; - - float GetDevicePixelRatio(); - void SourceToolBarActionsSetEnabled(); - - std::string lastScreenshot; - std::string lastReplay; - - void UpdatePreviewOverflowSettings(); - void UpdatePreviewScrollbars(); - - bool streamingStarting = false; - - bool recordingStarted = false; - bool isRecordingPausable = false; - bool recordingPaused = false; - - bool restartingVCam = false; - -public slots: - void DeferSaveBegin(); - void DeferSaveEnd(); - - void DisplayStreamStartError(); - - void SetupBroadcast(); - - void StartStreaming(); - void StopStreaming(); - void ForceStopStreaming(); - - void StreamDelayStarting(int sec); - void StreamDelayStopping(int sec); - - void StreamingStart(); - void StreamStopping(); - void StreamingStop(int errorcode, QString last_error); - - void StartRecording(); - void StopRecording(); - - void RecordingStart(); - void RecordStopping(); - void RecordingStop(int code, QString last_error); - void RecordingFileChanged(QString lastRecordingPath); - - void ShowReplayBufferPauseWarning(); - void StartReplayBuffer(); - void StopReplayBuffer(); - - void ReplayBufferStart(); - void ReplayBufferSave(); - void ReplayBufferSaved(); - void ReplayBufferStopping(); - void ReplayBufferStop(int code); - - void StartVirtualCam(); - void StopVirtualCam(); - - void OnVirtualCamStart(); - void OnVirtualCamStop(int code); - - void SaveProjectDeferred(); - void SaveProject(); - - void SetTransition(OBSSource transition); - void OverrideTransition(OBSSource transition); - void TransitionToScene(OBSScene scene, bool force = false); - void TransitionToScene(OBSSource scene, bool force = false, bool quickTransition = false, int quickDuration = 0, - bool black = false, bool manual = false); - void SetCurrentScene(OBSSource scene, bool force = false); - - void UpdatePatronJson(const QString &text, const QString &error); - - void ShowContextBar(); - void HideContextBar(); - void PauseRecording(); - void UnpauseRecording(); - - void UpdateEditMenu(); - -private slots: - - void on_actionMainUndo_triggered(); - void on_actionMainRedo_triggered(); - - void AddSceneItem(OBSSceneItem item); - void AddScene(OBSSource source); - void RemoveScene(OBSSource source); - void RenameSources(OBSSource source, QString newName, QString prevName); - - void ActivateAudioSource(OBSSource source); - void DeactivateAudioSource(OBSSource source); - - void DuplicateSelectedScene(); - void RemoveSelectedScene(); - - void ToggleAlwaysOnTop(); - - void ReorderSources(OBSScene scene); - void RefreshSources(OBSScene scene); - - void ProcessHotkey(obs_hotkey_id id, bool pressed); - - void AddTransition(const char *id); - void RenameTransition(OBSSource transition); - void TransitionClicked(); - void TransitionStopped(); - void TransitionFullyStopped(); - void TriggerQuickTransition(int id); - - void SetDeinterlacingMode(); - void SetDeinterlacingOrder(); - - void SetScaleFilter(); - - void SetBlendingMethod(); - void SetBlendingMode(); - - void IconActivated(QSystemTrayIcon::ActivationReason reason); - void SetShowing(bool showing); - - void ToggleShowHide(); - - void HideAudioControl(); - void UnhideAllAudioControls(); - void ToggleHideMixer(); - - void MixerRenameSource(); - - void on_vMixerScrollArea_customContextMenuRequested(); - void on_hMixerScrollArea_customContextMenuRequested(); - - void on_actionCopySource_triggered(); - void on_actionPasteRef_triggered(); - void on_actionPasteDup_triggered(); - - void on_actionCopyFilters_triggered(); - void on_actionPasteFilters_triggered(); - void AudioMixerCopyFilters(); - void AudioMixerPasteFilters(); - void SourcePasteFilters(OBSSource source, OBSSource dstSource); - - void on_previewXScrollBar_valueChanged(int value); - void on_previewYScrollBar_valueChanged(int value); - - void PreviewScalingModeChanged(int value); - - void ColorChange(); - - SourceTreeItem *GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem); - - void on_actionShowAbout_triggered(); - - void EnablePreview(); - void DisablePreview(); - - void EnablePreviewProgram(); - void DisablePreviewProgram(); - - void SceneCopyFilters(); - void ScenePasteFilters(); - - void CheckDiskSpaceRemaining(); - void OpenSavedProjector(SavedProjectorInfo *info); - - void ResetStatsHotkey(); - - void SetImageIcon(const QIcon &icon); - void SetColorIcon(const QIcon &icon); - void SetSlideshowIcon(const QIcon &icon); - void SetAudioInputIcon(const QIcon &icon); - void SetAudioOutputIcon(const QIcon &icon); - void SetDesktopCapIcon(const QIcon &icon); - void SetWindowCapIcon(const QIcon &icon); - void SetGameCapIcon(const QIcon &icon); - void SetCameraIcon(const QIcon &icon); - void SetTextIcon(const QIcon &icon); - void SetMediaIcon(const QIcon &icon); - void SetBrowserIcon(const QIcon &icon); - void SetGroupIcon(const QIcon &icon); - void SetSceneIcon(const QIcon &icon); - void SetDefaultIcon(const QIcon &icon); - void SetAudioProcessOutputIcon(const QIcon &icon); - - void TBarChanged(int value); - void TBarReleased(); - - void LockVolumeControl(bool lock); - - void UpdateVirtualCamConfig(const VCamConfig &config); - void RestartVirtualCam(const VCamConfig &config); - void RestartingVirtualCam(); - -private: - /* OBS Callbacks */ - static void SceneReordered(void *data, calldata_t *params); - static void SceneRefreshed(void *data, calldata_t *params); - static void SceneItemAdded(void *data, calldata_t *params); - static void SourceCreated(void *data, calldata_t *params); - static void SourceRemoved(void *data, calldata_t *params); - static void SourceActivated(void *data, calldata_t *params); - static void SourceDeactivated(void *data, calldata_t *params); - static void SourceAudioActivated(void *data, calldata_t *params); - static void SourceAudioDeactivated(void *data, calldata_t *params); - static void SourceRenamed(void *data, calldata_t *params); - static void RenderMain(void *data, uint32_t cx, uint32_t cy); - - void ResizePreview(uint32_t cx, uint32_t cy); - - void AddSource(const char *id); - QMenu *CreateAddSourcePopupMenu(); - void AddSourcePopupMenu(const QPoint &pos); - void copyActionsDynamicProperties(); - - static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); - - void AutoRemux(QString input, bool no_show = false); - - void UpdateIsRecordingPausable(); - - bool IsFFmpegOutputToURL() const; - bool OutputPathValid(); - void OutputPathInvalidMessage(); - - bool LowDiskSpace(); - void DiskSpaceMessage(); - - OBSSource prevFTBSource = nullptr; - - float dpi = 1.0; - -public: - OBSSource GetProgramSource(); - OBSScene GetCurrentScene(); - - void SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n); - - inline OBSSource GetCurrentSceneSource() - { - OBSScene curScene = GetCurrentScene(); - return OBSSource(obs_scene_get_source(curScene)); - } - - obs_service_t *GetService(); - void SetService(obs_service_t *service); - - int GetTransitionDuration(); - int GetTbarPosition(); - - inline bool IsPreviewProgramMode() const { return os_atomic_load_bool(&previewProgramMode); } - - inline bool VCamEnabled() const { return vcamEnabled; } - - bool Active() const; - - void ResetUI(); - int ResetVideo(); - bool ResetAudio(); - - void ResetOutputs(); - - void RefreshVolumeColors(); - - void ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel); - - void NewProject(); - void LoadProject(); - - inline void GetDisplayRect(int &x, int &y, int &cx, int &cy) - { - x = previewX; - y = previewY; - cx = previewCX; - cy = previewCY; - } - - inline bool SavingDisabled() const { return disableSaving; } - - inline double GetCPUUsage() const { return os_cpu_usage_info_query(cpuUsageInfo); } - - void SaveService(); - bool LoadService(); - - inline Auth *GetAuth() { return auth.get(); } - - inline void EnableOutputs(bool enable) - { - if (enable) { - if (--disableOutputsRef < 0) - disableOutputsRef = 0; - } else { - disableOutputsRef++; - } - } - - QMenu *AddDeinterlacingMenu(QMenu *menu, obs_source_t *source); - QMenu *AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item); - void CreateSourcePopupMenu(int idx, bool preview); - - void UpdateTitleBar(); - - void SystemTrayInit(); - void SystemTray(bool firstStarted); - - void OpenSavedProjectors(); - - void CreateInteractionWindow(obs_source_t *source); - void CreatePropertiesWindow(obs_source_t *source); - void CreateFiltersWindow(obs_source_t *source); - void CreateEditTransformWindow(obs_sceneitem_t *item); - - QAction *AddDockWidget(QDockWidget *dock); - void AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser = false); - void RemoveDockWidget(const QString &name); - bool IsDockObjectNameUsed(const QString &name); - void AddCustomDockWidget(QDockWidget *dock); - - static OBSBasic *Get(); - - const char *GetCurrentOutputPath(); - - void DeleteProjector(OBSProjector *projector); - - static QList GetProjectorMenuMonitorsFormatted(); - template - static void AddProjectorMenuMonitors(QMenu *parent, Receiver *target, void (Receiver::*slot)(Args...)) - { - auto projectors = GetProjectorMenuMonitorsFormatted(); - for (int i = 0; i < projectors.size(); i++) { - QString str = projectors[i]; - QAction *action = parent->addAction(str, target, slot); - action->setProperty("monitor", i); - } - } - - QIcon GetSourceIcon(const char *id) const; - QIcon GetGroupIcon() const; - QIcon GetSceneIcon() const; - - OBSWeakSource copyFilter; - - void ShowStatusBarMessage(const QString &message); - - static OBSData BackupScene(obs_scene_t *scene, std::vector *sources = nullptr); - void CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data); - - static inline OBSData BackupScene(obs_source_t *scene_source, std::vector *sources = nullptr) - { - obs_scene_t *scene = obs_scene_from_source(scene_source); - return BackupScene(scene, sources); - } - - void CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array); - - void SetDisplayAffinity(QWindow *window); - - QColor GetSelectionColor() const; - inline bool Closing() { return closing; } - -protected: - virtual void closeEvent(QCloseEvent *event) override; - virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; - virtual void changeEvent(QEvent *event) override; - -private slots: - void on_actionFullscreenInterface_triggered(); - - void on_actionShow_Recordings_triggered(); - void on_actionRemux_triggered(); - void on_action_Settings_triggered(); - void on_actionShowMacPermissions_triggered(); - void on_actionShowMissingFiles_triggered(); - void on_actionAdvAudioProperties_triggered(); - void on_actionMixerToolbarAdvAudio_triggered(); - void on_actionMixerToolbarMenu_triggered(); - void on_actionShowLogs_triggered(); - void on_actionUploadCurrentLog_triggered(); - void on_actionUploadLastLog_triggered(); - void on_actionViewCurrentLog_triggered(); - void on_actionCheckForUpdates_triggered(); - void on_actionRepair_triggered(); - void on_actionShowWhatsNew_triggered(); - void on_actionRestartSafe_triggered(); - - void on_actionShowCrashLogs_triggered(); - void on_actionUploadLastCrashLog_triggered(); - - void on_actionEditTransform_triggered(); - void on_actionCopyTransform_triggered(); - void on_actionPasteTransform_triggered(); - void on_actionRotate90CW_triggered(); - void on_actionRotate90CCW_triggered(); - void on_actionRotate180_triggered(); - void on_actionFlipHorizontal_triggered(); - void on_actionFlipVertical_triggered(); - void on_actionFitToScreen_triggered(); - void on_actionStretchToScreen_triggered(); - void on_actionCenterToScreen_triggered(); - void on_actionVerticalCenter_triggered(); - void on_actionHorizontalCenter_triggered(); - void on_actionSceneFilters_triggered(); - - void on_OBSBasic_customContextMenuRequested(const QPoint &pos); - - void on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *prev); - void on_scenes_customContextMenuRequested(const QPoint &pos); - void GridActionClicked(); - void on_actionSceneListMode_triggered(); - void on_actionSceneGridMode_triggered(); - void on_actionAddScene_triggered(); - void on_actionRemoveScene_triggered(); - void on_actionSceneUp_triggered(); - void on_actionSceneDown_triggered(); - void on_sources_customContextMenuRequested(const QPoint &pos); - void on_scenes_itemDoubleClicked(QListWidgetItem *item); - void on_actionAddSource_triggered(); - void on_actionRemoveSource_triggered(); - void on_actionInteract_triggered(); - void on_actionSourceProperties_triggered(); - void on_actionSourceUp_triggered(); - void on_actionSourceDown_triggered(); - - void on_actionMoveUp_triggered(); - void on_actionMoveDown_triggered(); - void on_actionMoveToTop_triggered(); - void on_actionMoveToBottom_triggered(); - - void on_actionLockPreview_triggered(); - - void on_scalingMenu_aboutToShow(); - void on_actionScaleWindow_triggered(); - void on_actionScaleCanvas_triggered(); - void on_actionScaleOutput_triggered(); - - void Screenshot(OBSSource source_ = nullptr); - void ScreenshotSelectedSource(); - void ScreenshotProgram(); - void ScreenshotScene(); - - void on_actionHelpPortal_triggered(); - void on_actionWebsite_triggered(); - void on_actionDiscord_triggered(); - void on_actionReleaseNotes_triggered(); - - void on_preview_customContextMenuRequested(); - void ProgramViewContextMenuRequested(); - void on_previewDisabledWidget_customContextMenuRequested(); - - void on_actionShowSettingsFolder_triggered(); - void on_actionShowProfileFolder_triggered(); - - void on_actionAlwaysOnTop_triggered(); - - void on_toggleListboxToolbars_toggled(bool visible); - void on_toggleContextBar_toggled(bool visible); - void on_toggleStatusBar_toggled(bool visible); - void on_toggleSourceIcons_toggled(bool visible); - - void on_transitions_currentIndexChanged(int index); - void on_transitionAdd_clicked(); - void on_transitionRemove_clicked(); - void on_transitionProps_clicked(); - void on_transitionDuration_valueChanged(); - - void ShowTransitionProperties(); - void HideTransitionProperties(); - - // Source Context Buttons - void on_sourcePropertiesButton_clicked(); - void on_sourceFiltersButton_clicked(); - void on_sourceInteractButton_clicked(); - - void on_autoConfigure_triggered(); - void on_stats_triggered(); - - void on_resetUI_triggered(); - void on_resetDocks_triggered(bool force = false); - void on_lockDocks_toggled(bool lock); - void on_multiviewProjectorWindowed_triggered(); - void on_sideDocks_toggled(bool side); - - void logUploadFinished(const QString &text, const QString &error); - void crashUploadFinished(const QString &text, const QString &error); - void openLogDialog(const QString &text, const bool crash); - - void updateCheckFinished(); - - void MoveSceneToTop(); - void MoveSceneToBottom(); - - void EditSceneName(); - void EditSceneItemName(); - - void SceneNameEdited(QWidget *editor); - - void OpenSceneFilters(); - void OpenFilters(OBSSource source = nullptr); - void OpenProperties(OBSSource source = nullptr); - void OpenInteraction(OBSSource source = nullptr); - void OpenEditTransform(OBSSceneItem item = nullptr); - - void EnablePreviewDisplay(bool enable); - void TogglePreview(); - - void OpenStudioProgramProjector(); - void OpenPreviewProjector(); - void OpenSourceProjector(); - void OpenMultiviewProjector(); - void OpenSceneProjector(); - - void OpenStudioProgramWindow(); - void OpenPreviewWindow(); - void OpenSourceWindow(); - void OpenSceneWindow(); - - void StackedMixerAreaContextMenuRequested(); - - void ResizeOutputSizeOfSource(); - - void RepairOldExtraDockName(); - void RepairCustomExtraDockName(); - - /* Stream action (start/stop) slot */ - void StreamActionTriggered(); - - /* Record action (start/stop) slot */ - void RecordActionTriggered(); - - /* Record pause (pause/unpause) slot */ - void RecordPauseToggled(); - - /* Replay Buffer action (start/stop) slot */ - void ReplayBufferActionTriggered(); - - /* Virtual Cam action (start/stop) slots */ - void VirtualCamActionTriggered(); - - void OpenVirtualCamConfig(); - - /* Studio Mode toggle slot */ - void TogglePreviewProgramMode(); - -public slots: - void on_actionResetTransform_triggered(); - - bool StreamingActive(); - bool RecordingActive(); - bool ReplayBufferActive(); - bool VirtualCamActive(); - - void ClearContextBar(); - void UpdateContextBar(bool force = false); - void UpdateContextBarDeferred(bool force = false); - void UpdateContextBarVisibility(); - -signals: - /* Streaming signals */ - void StreamingPreparing(); - void StreamingStarting(bool broadcastAutoStart); - void StreamingStarted(bool withDelay = false); - void StreamingStopping(); - void StreamingStopped(bool withDelay = false); - - /* Broadcast Flow signals */ - void BroadcastFlowEnabled(bool enabled); - void BroadcastStreamReady(bool ready); - void BroadcastStreamActive(); - void BroadcastStreamStarted(bool autoStop); - - /* Recording signals */ - void RecordingStarted(bool pausable = false); - void RecordingPaused(); - void RecordingUnpaused(); - void RecordingStopping(); - void RecordingStopped(); - - /* Replay Buffer signals */ - void ReplayBufEnabled(bool enabled); - void ReplayBufStarted(); - void ReplayBufStopping(); - void ReplayBufStopped(); - - /* Virtual Camera signals */ - void VirtualCamEnabled(); - void VirtualCamStarted(); - void VirtualCamStopped(); - - /* Studio Mode signal */ - void PreviewProgramModeChanged(bool enabled); - void CanvasResized(uint32_t width, uint32_t height); - void OutputResized(uint32_t width, uint32_t height); - - /* Preview signals */ - void PreviewXScrollBarMoved(int value); - void PreviewYScrollBarMoved(int value); - -private: - std::unique_ptr ui; - - QPointer controlsDock; - -public: - /* `undo_s` needs to be declared after `ui` to prevent an uninitialized - * warning for `ui` while initializing `undo_s`. */ - undo_stack undo_s; - - explicit OBSBasic(QWidget *parent = 0); - virtual ~OBSBasic(); - - virtual void OBSInit() override; - - virtual config_t *Config() const override; - - virtual int GetProfilePath(char *path, size_t size, const char *file) const override; - - static void InitBrowserPanelSafeBlock(); -#ifdef YOUTUBE_ENABLED - void NewYouTubeAppDock(); - void DeleteYouTubeAppDock(); - YouTubeAppDock *GetYouTubeAppDock(); -#endif - // MARK: - Generic UI Helper Functions - OBSPromptResult PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback); - - // MARK: - OBS Profile Management -private: - OBSProfileCache profiles{}; - - void SetupNewProfile(const std::string &profileName, bool useWizard = false); - void SetupDuplicateProfile(const std::string &profileName); - void SetupRenameProfile(const std::string &profileName); - - const OBSProfile &CreateProfile(const std::string &profileName); - void RemoveProfile(OBSProfile profile); - - void ChangeProfile(); - - void RefreshProfileCache(); - - void RefreshProfiles(bool refreshCache = false); - - void ActivateProfile(const OBSProfile &profile, bool reset = false); - void UpdateProfileEncoders(); - std::vector GetRestartRequirements(const ConfigFile &config) const; - void ResetProfileData(); - void CheckForSimpleModeX264Fallback(); - -public: - inline const OBSProfileCache &GetProfileCache() const noexcept { return profiles; }; - - const OBSProfile &GetCurrentProfile() const; - - std::optional GetProfileByName(const std::string &profileName) const; - std::optional GetProfileByDirectoryName(const std::string &directoryName) const; - -private slots: - void on_actionNewProfile_triggered(); - void on_actionDupProfile_triggered(); - void on_actionRenameProfile_triggered(); - void on_actionRemoveProfile_triggered(bool skipConfirmation = false); - void on_actionImportProfile_triggered(); - void on_actionExportProfile_triggered(); - -public slots: - bool CreateNewProfile(const QString &name); - bool CreateDuplicateProfile(const QString &name); - void DeleteProfile(const QString &profileName); - - // MARK: - OBS Scene Collection Management -private: - OBSSceneCollectionCache collections{}; - - void SetupNewSceneCollection(const std::string &collectionName); - void SetupDuplicateSceneCollection(const std::string &collectionName); - void SetupRenameSceneCollection(const std::string &collectionName); - - const OBSSceneCollection &CreateSceneCollection(const std::string &collectionName); - void RemoveSceneCollection(OBSSceneCollection collection); - - bool CreateDuplicateSceneCollection(const QString &name); - void DeleteSceneCollection(const QString &name); - void ChangeSceneCollection(); - - void RefreshSceneCollectionCache(); - - void RefreshSceneCollections(bool refreshCache = false); - void ActivateSceneCollection(const OBSSceneCollection &collection); - -public: - inline const OBSSceneCollectionCache &GetSceneCollectionCache() const noexcept { return collections; }; - - const OBSSceneCollection &GetCurrentSceneCollection() const; - - std::optional GetSceneCollectionByName(const std::string &collectionName) const; - std::optional GetSceneCollectionByFileName(const std::string &fileName) const; - -private slots: - void on_actionNewSceneCollection_triggered(); - void on_actionDupSceneCollection_triggered(); - void on_actionRenameSceneCollection_triggered(); - void on_actionRemoveSceneCollection_triggered(bool skipConfirmation = false); - void on_actionImportSceneCollection_triggered(); - void on_actionExportSceneCollection_triggered(); - void on_actionRemigrateSceneCollection_triggered(); - -public slots: - bool CreateNewSceneCollection(const QString &name); -}; - -extern bool cef_js_avail; class SceneRenameDelegate : public QStyledItemDelegate { Q_OBJECT diff --git a/frontend/utility/ScreenshotObj.cpp b/frontend/utility/ScreenshotObj.cpp index a0b3622d1..5b7bc86ca 100644 --- a/frontend/utility/ScreenshotObj.cpp +++ b/frontend/utility/ScreenshotObj.cpp @@ -15,8 +15,9 @@ along with this program. If not, see . ******************************************************************************/ -#include "window-basic-main.hpp" -#include "screenshot-obj.hpp" +#include "ScreenshotObj.hpp" + +#include #include @@ -27,9 +28,9 @@ #pragma comment(lib, "windowscodecs.lib") #endif -static void ScreenshotTick(void *param, float); +#include "moc_ScreenshotObj.cpp" -/* ========================================================================= */ +static void ScreenshotTick(void *param, float); ScreenshotObj::ScreenshotObj(obs_source_t *source) : weakSource(OBSGetWeakRef(source)) { @@ -278,8 +279,6 @@ void ScreenshotObj::MuxAndFinish() deleteLater(); } -/* ========================================================================= */ - #define STAGE_SCREENSHOT 0 #define STAGE_DOWNLOAD 1 #define STAGE_COPY_AND_SAVE 2 @@ -313,35 +312,3 @@ static void ScreenshotTick(void *param, float) data->stage++; } - -void OBSBasic::Screenshot(OBSSource source) -{ - if (!!screenshotData) { - blog(LOG_WARNING, "Cannot take new screenshot, " - "screenshot currently in progress"); - return; - } - - screenshotData = new ScreenshotObj(source); -} - -void OBSBasic::ScreenshotSelectedSource() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (item) { - Screenshot(obs_sceneitem_get_source(item)); - } else { - blog(LOG_INFO, "Could not take a source screenshot: " - "no source selected"); - } -} - -void OBSBasic::ScreenshotProgram() -{ - Screenshot(GetProgramSource()); -} - -void OBSBasic::ScreenshotScene() -{ - Screenshot(GetCurrentSceneSource()); -} diff --git a/frontend/utility/SimpleOutput.cpp b/frontend/utility/SimpleOutput.cpp index 30d203174..4e783dd7a 100644 --- a/frontend/utility/SimpleOutput.cpp +++ b/frontend/utility/SimpleOutput.cpp @@ -1,195 +1,13 @@ -#include -#include -#include -#include -#include +#include "SimpleOutput.hpp" + +#include +#include +#include + #include -#include "audio-encoders.hpp" -#include "multitrack-video-error.hpp" -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" -#include "window-basic-vcam.hpp" using namespace std; -extern bool EncoderAvailable(const char *encoder); - -volatile bool streaming_active = false; -volatile bool recording_active = false; -volatile bool recording_paused = false; -volatile bool replaybuf_active = false; -volatile bool virtualcam_active = false; - -#define RTMP_PROTOCOL "rtmp" -#define SRT_PROTOCOL "srt" -#define RIST_PROTOCOL "rist" - -static void OBSStreamStarting(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - return; - - output->delayActive = true; - QMetaObject::invokeMethod(output->main, "StreamDelayStarting", Q_ARG(int, sec)); -} - -static void OBSStreamStopping(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - QMetaObject::invokeMethod(output->main, "StreamStopping"); - else - QMetaObject::invokeMethod(output->main, "StreamDelayStopping", Q_ARG(int, sec)); -} - -static void OBSStartStreaming(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->streamingActive = true; - os_atomic_set_bool(&streaming_active, true); - QMetaObject::invokeMethod(output->main, "StreamingStart"); -} - -static void OBSStopStreaming(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->streamingActive = false; - output->delayActive = false; - output->multitrackVideoActive = false; - os_atomic_set_bool(&streaming_active, false); - QMetaObject::invokeMethod(output->main, "StreamingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSStartRecording(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->recordingActive = true; - os_atomic_set_bool(&recording_active, true); - QMetaObject::invokeMethod(output->main, "RecordingStart"); -} - -static void OBSStopRecording(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->recordingActive = false; - os_atomic_set_bool(&recording_active, false); - os_atomic_set_bool(&recording_paused, false); - QMetaObject::invokeMethod(output->main, "RecordingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSRecordStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "RecordStopping"); -} - -static void OBSRecordFileChanged(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - const char *next_file = calldata_string(params, "next_file"); - - QString arg_last_file = QString::fromUtf8(output->lastRecordingPath.c_str()); - - QMetaObject::invokeMethod(output->main, "RecordingFileChanged", Q_ARG(QString, arg_last_file)); - - output->lastRecordingPath = next_file; -} - -static void OBSStartReplayBuffer(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->replayBufferActive = true; - os_atomic_set_bool(&replaybuf_active, true); - QMetaObject::invokeMethod(output->main, "ReplayBufferStart"); -} - -static void OBSStopReplayBuffer(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->replayBufferActive = false; - os_atomic_set_bool(&replaybuf_active, false); - QMetaObject::invokeMethod(output->main, "ReplayBufferStop", Q_ARG(int, code)); -} - -static void OBSReplayBufferStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferStopping"); -} - -static void OBSReplayBufferSaved(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferSaved", Qt::QueuedConnection); -} - -static void OBSStartVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->virtualCamActive = true; - os_atomic_set_bool(&virtualcam_active, true); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStart"); -} - -static void OBSStopVirtualCam(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->virtualCamActive = false; - os_atomic_set_bool(&virtualcam_active, false); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", Q_ARG(int, code)); -} - -static void OBSDeactivateVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->DestroyVirtualCamView(); -} - -/* ------------------------------------------------------------------------ */ - -struct StartMultitrackVideoStreamingGuard { - StartMultitrackVideoStreamingGuard() { future = guard.get_future().share(); }; - ~StartMultitrackVideoStreamingGuard() { guard.set_value(); } - - std::shared_future GetFuture() const { return future; } - - static std::shared_future MakeReadyFuture() - { - StartMultitrackVideoStreamingGuard guard; - return guard.GetFuture(); - } - -private: - std::promise guard; - std::shared_future future; -}; - -/* ------------------------------------------------------------------------ */ - static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) { const char *id_ = GetSimpleAACEncoderForBitrate(bitrate); @@ -226,291 +44,7 @@ static bool CreateSimpleOpusEncoder(OBSEncoder &res, int bitrate, const char *na return false; } -static inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, - const char *prot_test2 = nullptr) -{ - return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && - (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; -} - -static bool return_first_id(void *data, const char *id) -{ - const char **output = (const char **)data; - - *output = id; - return false; -} - -static const char *GetStreamOutputType(const obs_service_t *service) -{ - const char *protocol = obs_service_get_protocol(service); - const char *output = nullptr; - - if (!protocol) { - blog(LOG_WARNING, "The service '%s' has no protocol set", obs_service_get_id(service)); - return nullptr; - } - - if (!obs_is_output_protocol_registered(protocol)) { - blog(LOG_WARNING, "The protocol '%s' is not registered", protocol); - return nullptr; - } - - /* Check if the service has a preferred output type */ - output = obs_service_get_preferred_output_type(service); - if (output) { - if ((obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0) - return output; - - blog(LOG_WARNING, "The output '%s' is not registered, fallback to another one", output); - } - - /* Otherwise, prefer first-party output types */ - if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) { - return "rtmp_output"; - } else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) { - return "ffmpeg_hls_muxer"; - } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", "RIST")) { - return "ffmpeg_mpegts_muxer"; - } - - /* If third-party protocol, use the first enumerated type */ - obs_enum_output_types_with_protocol(protocol, &output, return_first_id); - if (output) - return output; - - blog(LOG_WARNING, "No output compatible with the service '%s' is registered", obs_service_get_id(service)); - - return nullptr; -} - -/* ------------------------------------------------------------------------ */ - -inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) -{ - if (main->vcamEnabled) { - virtualCam = obs_output_create(VIRTUAL_CAM_ID, "virtualcam_output", nullptr, nullptr); - - signal_handler_t *signal = obs_output_get_signal_handler(virtualCam); - startVirtualCam.Connect(signal, "start", OBSStartVirtualCam, this); - stopVirtualCam.Connect(signal, "stop", OBSStopVirtualCam, this); - deactivateVirtualCam.Connect(signal, "deactivate", OBSDeactivateVirtualCam, this); - } - - auto multitrack_enabled = config_get_bool(main->Config(), "Stream1", "EnableMultitrackVideo"); - if (!config_has_user_value(main->Config(), "Stream1", "EnableMultitrackVideo")) { - auto service = main_->GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - multitrack_enabled = obs_data_has_user_value(settings, "multitrack_video_configuration_url"); - } - if (multitrack_enabled) - multitrackVideo = make_unique(); -} - -extern void log_vcam_changed(const VCamConfig &config, bool starting); - -bool BasicOutputHandler::StartVirtualCam() -{ - if (!main->vcamEnabled) - return false; - - bool typeIsProgram = main->vcamConfig.type == VCamOutputType::ProgramView; - - if (!virtualCamView && !typeIsProgram) - virtualCamView = obs_view_create(); - - UpdateVirtualCamOutputSource(); - - if (!virtualCamVideo) { - virtualCamVideo = typeIsProgram ? obs_get_video() : obs_view_add(virtualCamView); - - if (!virtualCamVideo) - return false; - } - - obs_output_set_media(virtualCam, virtualCamVideo, obs_get_audio()); - if (!Active()) - SetupOutputs(); - - bool success = obs_output_start(virtualCam); - if (!success) { - QString errorReason; - - const char *error = obs_output_get_last_error(virtualCam); - if (error) { - errorReason = QT_UTF8(error); - } else { - errorReason = QTStr("Output.StartFailedGeneric"); - } - - QMessageBox::critical(main, QTStr("Output.StartVirtualCamFailed"), errorReason); - - DestroyVirtualCamView(); - } - - log_vcam_changed(main->vcamConfig, true); - - return success; -} - -void BasicOutputHandler::StopVirtualCam() -{ - if (main->vcamEnabled) { - obs_output_stop(virtualCam); - } -} - -bool BasicOutputHandler::VirtualCamActive() const -{ - if (main->vcamEnabled) { - return obs_output_active(virtualCam); - } - return false; -} - -void BasicOutputHandler::UpdateVirtualCamOutputSource() -{ - if (!main->vcamEnabled || !virtualCamView) - return; - - OBSSourceAutoRelease source; - - switch (main->vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - DestroyVirtualCameraScene(); - return; - case VCamOutputType::PreviewOutput: { - DestroyVirtualCameraScene(); - OBSSource s = main->GetCurrentSceneSource(); - obs_source_get_ref(s); - source = s.Get(); - break; - } - case VCamOutputType::SceneOutput: - DestroyVirtualCameraScene(); - source = obs_get_source_by_name(main->vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - OBSSourceAutoRelease s = obs_get_source_by_name(main->vcamConfig.source.c_str()); - - if (!vCamSourceScene) - vCamSourceScene = obs_scene_create_private("vcam_source"); - source = obs_source_get_ref(obs_scene_get_source(vCamSourceScene)); - - if (vCamSourceSceneItem && (obs_sceneitem_get_source(vCamSourceSceneItem) != s)) { - obs_sceneitem_remove(vCamSourceSceneItem); - vCamSourceSceneItem = nullptr; - } - - if (!vCamSourceSceneItem) { - vCamSourceSceneItem = obs_scene_add(vCamSourceScene, s); - - obs_sceneitem_set_bounds_type(vCamSourceSceneItem, OBS_BOUNDS_SCALE_INNER); - obs_sceneitem_set_bounds_alignment(vCamSourceSceneItem, OBS_ALIGN_CENTER); - - const struct vec2 size = { - (float)obs_source_get_width(source), - (float)obs_source_get_height(source), - }; - obs_sceneitem_set_bounds(vCamSourceSceneItem, &size); - } - break; - } - - OBSSourceAutoRelease current = obs_view_get_source(virtualCamView, 0); - if (source != current) - obs_view_set_source(virtualCamView, 0, source); -} - -void BasicOutputHandler::DestroyVirtualCamView() -{ - if (main->vcamConfig.type == VCamOutputType::ProgramView) { - virtualCamVideo = nullptr; - return; - } - - obs_view_remove(virtualCamView); - obs_view_set_source(virtualCamView, 0, nullptr); - virtualCamVideo = nullptr; - - obs_view_destroy(virtualCamView); - virtualCamView = nullptr; - - DestroyVirtualCameraScene(); -} - -void BasicOutputHandler::DestroyVirtualCameraScene() -{ - if (!vCamSourceScene) - return; - - obs_scene_release(vCamSourceScene); - vCamSourceScene = nullptr; - vCamSourceSceneItem = nullptr; -} - -/* ------------------------------------------------------------------------ */ - -struct SimpleOutput : BasicOutputHandler { - OBSEncoder audioStreaming; - OBSEncoder videoStreaming; - OBSEncoder audioRecording; - OBSEncoder audioArchive; - OBSEncoder videoRecording; - OBSEncoder audioTrack[MAX_AUDIO_MIXES]; - - string videoEncoder; - string videoQuality; - bool usingRecordingPreset = false; - bool recordingConfigured = false; - bool ffmpegOutput = false; - bool lowCPUx264 = false; - - SimpleOutput(OBSBasic *main_); - - int CalcCRF(int crf); - - void UpdateRecordingSettings_x264_crf(int crf); - void UpdateRecordingSettings_qsv11(int crf, bool av1); - void UpdateRecordingSettings_nvenc(int cqp); - void UpdateRecordingSettings_nvenc_hevc_av1(int cqp); - void UpdateRecordingSettings_amd_cqp(int cqp); - void UpdateRecordingSettings_apple(int quality); -#ifdef ENABLE_HEVC - void UpdateRecordingSettings_apple_hevc(int quality); -#endif - void UpdateRecordingSettings(); - void UpdateRecordingAudioSettings(); - virtual void Update() override; - - void SetupOutputs() override; - int GetAudioBitrate() const; - - void LoadRecordingPreset_Lossy(const char *encoder); - void LoadRecordingPreset_Lossless(); - void LoadRecordingPreset(); - - void LoadStreamingPreset_Lossy(const char *encoder); - - void UpdateRecording(); - bool ConfigureRecording(bool useReplayBuffer); - - bool IsVodTrackEnabled(obs_service_t *service); - void SetupVodTrack(obs_service_t *service); - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; -}; +extern bool EncoderAvailable(const char *encoder); void SimpleOutput::LoadRecordingPreset_Lossless() { @@ -1025,21 +559,6 @@ inline void SimpleOutput::SetupOutputs() } } -const char *FindAudioEncoderFromCodec(const char *type) -{ - const char *alt_enc_id = nullptr; - size_t i = 0; - - while (obs_enum_encoder_types(i++, &alt_enc_id)) { - const char *codec = obs_get_encoder_codec(alt_enc_id); - if (strcmp(type, codec) == 0) { - return alt_enc_id; - } - } - - return nullptr; -} - std::shared_future SimpleOutput::SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation) { if (!Active()) @@ -1105,24 +624,6 @@ std::shared_future SimpleOutput::SetupStreaming(obs_service_t *service, Se }); } -static inline bool ServiceSupportsVodTrack(const char *service); - -static void clear_archive_encoder(obs_output_t *output, const char *expected_name) -{ - obs_encoder_t *last = obs_output_get_audio_encoder(output, 1); - bool clear = false; - - /* ensures that we don't remove twitch's soundtrack encoder */ - if (last) { - const char *name = obs_encoder_get_name(last); - clear = name && strcmp(name, expected_name) == 0; - obs_encoder_release(last); - } - - if (clear) - obs_output_set_audio_encoder(output, nullptr, 1); -} - bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service) { bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); @@ -1401,1141 +902,3 @@ bool SimpleOutput::ReplayBufferActive() const { return obs_output_active(replayBuffer); } - -/* ------------------------------------------------------------------------ */ - -struct AdvancedOutput : BasicOutputHandler { - OBSEncoder streamAudioEnc; - OBSEncoder streamArchiveEnc; - OBSEncoder streamTrack[MAX_AUDIO_MIXES]; - OBSEncoder recordTrack[MAX_AUDIO_MIXES]; - OBSEncoder videoStreaming; - OBSEncoder videoRecording; - - bool ffmpegOutput; - bool ffmpegRecording; - bool useStreamEncoder; - bool useStreamAudioEncoder; - bool usesBitrate = false; - - AdvancedOutput(OBSBasic *main_); - - inline void UpdateStreamSettings(); - inline void UpdateRecordingSettings(); - inline void UpdateAudioSettings(); - virtual void Update() override; - - inline std::optional VodTrackMixerIdx(obs_service_t *service); - inline void SetupVodTrack(obs_service_t *service); - - inline void SetupStreaming(); - inline void SetupRecording(); - inline void SetupFFmpeg(); - void SetupOutputs() override; - int GetAudioBitrate(size_t i, const char *id) const; - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; - bool allowsMultiTrack(); -}; - -static OBSData GetDataFromJsonFile(const char *jsonFile) -{ - const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); - - const OBSProfile ¤tProfile = basic->GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(jsonFile); - - OBSDataAutoRelease data = nullptr; - - if (!jsonFilePath.empty()) { - BPtr jsonData = os_quick_read_utf8_file(jsonFilePath.u8string().c_str()); - - if (!!jsonData) { - data = obs_data_create_from_json(jsonData); - } - } - - if (!data) { - data = obs_data_create(); - } - - return data.Get(); -} - -static void ApplyEncoderDefaults(OBSData &settings, const obs_encoder_t *encoder) -{ - OBSData dataRet = obs_encoder_get_defaults(encoder); - obs_data_release(dataRet); - - if (!!settings) - obs_data_apply(dataRet, settings); - settings = std::move(dataRet); -} - -#define ADV_ARCHIVE_NAME "adv_archive_audio" - -#ifdef __APPLE__ -static void translate_macvth264_encoder(const char *&encoder) -{ - if (strcmp(encoder, "vt_h264_hw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264.gva"; - } else if (strcmp(encoder, "vt_h264_sw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264"; - } -} -#endif - -AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *recType = config_get_string(main->Config(), "AdvOut", "RecType"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - const char *streamAudioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recordEncoder = config_get_string(main->Config(), "AdvOut", "RecEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); -#ifdef __APPLE__ - translate_macvth264_encoder(streamEncoder); - translate_macvth264_encoder(recordEncoder); -#endif - - ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - ffmpegRecording = ffmpegOutput && config_get_bool(main->Config(), "AdvOut", "FFOutputToFile"); - useStreamEncoder = astrcmpi(recordEncoder, "none") == 0; - useStreamAudioEncoder = astrcmpi(recAudioEncoder, "none") == 0; - - OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); - OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); - - if (ffmpegOutput) { - fileOutput = obs_output_create("ffmpeg_output", "adv_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(advanced output)"; - } else { - bool useReplayBuffer = config_get_bool(main->Config(), "AdvOut", "RecRB"); - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool native_muxer = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(native_muxer ? "mp4_output" : "ffmpeg_muxer", "adv_file_output", nullptr, - nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(advanced output)"; - - if (!useStreamEncoder) { - videoRecording = obs_video_encoder_create(recordEncoder, "advanced_video_recording", - recordEncSettings, nullptr); - if (!videoRecording) - throw "Failed to create recording video " - "encoder (advanced output)"; - obs_encoder_release(videoRecording); - } - } - - videoStreaming = obs_video_encoder_create(streamEncoder, "advanced_video_stream", streamEncSettings, nullptr); - if (!videoStreaming) - throw "Failed to create streaming video encoder " - "(advanced output)"; - obs_encoder_release(videoStreaming); - - const char *rate_control = - obs_data_get_string(useStreamEncoder ? streamEncSettings : recordEncSettings, "rate_control"); - if (!rate_control) - rate_control = ""; - usesBitrate = astrcmpi(rate_control, "CBR") == 0 || astrcmpi(rate_control, "VBR") == 0 || - astrcmpi(rate_control, "ABR") == 0; - - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[19]; - snprintf(name, sizeof(name), "adv_record_audio_%d", i); - - recordTrack[i] = obs_audio_encoder_create(useStreamAudioEncoder ? streamAudioEncoder : recAudioEncoder, - name, nullptr, i, nullptr); - - if (!recordTrack[i]) { - throw "Failed to create audio encoder " - "(advanced output)"; - } - - obs_encoder_release(recordTrack[i]); - - snprintf(name, sizeof(name), "adv_stream_audio_%d", i); - streamTrack[i] = obs_audio_encoder_create(streamAudioEncoder, name, nullptr, i, nullptr); - - if (!streamTrack[i]) { - throw "Failed to create streaming audio encoders " - "(advanced output)"; - } - - obs_encoder_release(streamTrack[i]); - } - - std::string id; - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - streamAudioEnc = - obs_audio_encoder_create(streamAudioEncoder, "adv_stream_audio", nullptr, streamTrackIndex, nullptr); - if (!streamAudioEnc) - throw "Failed to create streaming audio encoder " - "(advanced output)"; - obs_encoder_release(streamAudioEnc); - - id = ""; - int vodTrack = config_get_int(main->Config(), "AdvOut", "VodTrackIndex") - 1; - streamArchiveEnc = obs_audio_encoder_create(streamAudioEncoder, ADV_ARCHIVE_NAME, nullptr, vodTrack, nullptr); - if (!streamArchiveEnc) - throw "Failed to create archive audio encoder " - "(advanced output)"; - obs_encoder_release(streamArchiveEnc); - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); - recordFileChanged.Connect(obs_output_get_signal_handler(fileOutput), "file_changed", OBSRecordFileChanged, - this); -} - -void AdvancedOutput::UpdateStreamSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - bool dynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - - OBSData settings = GetDataFromJsonFile("streamEncoder.json"); - ApplyEncoderDefaults(settings, videoStreaming); - - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings, "bitrate"); - int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(settings, "bitrate", bitrate); - } - - int enforced_keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - if (keyint_sec != 0 && keyint_sec < enforced_keyint_sec) - obs_data_set_int(settings, "keyint_sec", keyint_sec); - } else { - blog(LOG_WARNING, "User is ignoring service settings."); - } - - if (dynBitrate && strstr(streamEncoder, "nvenc") != nullptr) - obs_data_set_bool(settings, "lookahead", false); - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, settings); -} - -inline void AdvancedOutput::UpdateRecordingSettings() -{ - OBSData settings = GetDataFromJsonFile("recordEncoder.json"); - obs_encoder_update(videoRecording, settings); -} - -void AdvancedOutput::Update() -{ - UpdateStreamSettings(); - if (!useStreamEncoder && !ffmpegOutput) - UpdateRecordingSettings(); - UpdateAudioSettings(); -} - -static inline bool ServiceSupportsVodTrack(const char *service) -{ - static const char *vodTrackServices[] = {"Twitch"}; - - for (const char *vodTrackService : vodTrackServices) { - if (astrcmpi(vodTrackService, service) == 0) - return true; - } - - return false; -} - -inline bool AdvancedOutput::allowsMultiTrack() -{ - const char *protocol = nullptr; - obs_service_t *service_obj = main->GetService(); - protocol = obs_service_get_protocol(service_obj); - if (!protocol) - return false; - return astrcmpi_n(protocol, SRT_PROTOCOL, strlen(SRT_PROTOCOL)) == 0 || - astrcmpi_n(protocol, RIST_PROTOCOL, strlen(RIST_PROTOCOL)) == 0; -} - -inline void AdvancedOutput::SetupStreaming() -{ - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RescaleFilter"); - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - bool is_multitrack_output = allowsMultiTrack(); - - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - obs_encoder_set_scaled_size(videoStreaming, cx, cy); - obs_encoder_set_gpu_scale_type(videoStreaming, (obs_scale_type)rescaleFilter); - - const char *id = obs_service_get_id(main->GetService()); - if (strcmp(id, "rtmp_custom") == 0) { - OBSDataAutoRelease settings = obs_data_create(); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - obs_encoder_update(videoStreaming, settings); - } -} - -inline void AdvancedOutput::SetupRecording() -{ - const char *path = config_get_string(main->Config(), "AdvOut", "RecFilePath"); - const char *mux = config_get_string(main->Config(), "AdvOut", "RecMuxerCustom"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RecRescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RecRescaleFilter"); - int tracks; - - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); - - bool is_fragmented = strncmp(recFormat, "fragmented", 10) == 0; - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv) - tracks = config_get_int(main->Config(), "AdvOut", "FLVTrack"); - else - tracks = config_get_int(main->Config(), "AdvOut", "RecTracks"); - - OBSDataAutoRelease settings = obs_data_create(); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - - /* Hack to allow recordings without any audio tracks selected. It is no - * longer possible to select such a configuration in settings, but legacy - * configurations might still have this configured and we don't want to - * just break them. */ - if (tracks == 0) - tracks = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - - if (useStreamEncoder) { - obs_output_set_video_encoder(fileOutput, videoStreaming); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoStreaming); - } else { - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - obs_encoder_set_scaled_size(videoRecording, cx, cy); - obs_encoder_set_gpu_scale_type(videoRecording, (obs_scale_type)rescaleFilter); - obs_output_set_video_encoder(fileOutput, videoRecording); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoRecording); - } - - if (!flv) { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[i], idx); - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[i], idx); - idx++; - } - } - } else if (flv && tracks != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[tracks - 1], idx); - - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[tracks - 1], idx); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - obs_data_set_string(settings, "path", path); - obs_output_update(fileOutput, settings); - if (replayBuffer) - obs_output_update(replayBuffer, settings); -} - -inline void AdvancedOutput::SetupFFmpeg() -{ - const char *url = config_get_string(main->Config(), "AdvOut", "FFURL"); - int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate"); - int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize"); - bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "FFRescaleRes"); - const char *formatName = config_get_string(main->Config(), "AdvOut", "FFFormat"); - const char *mimeType = config_get_string(main->Config(), "AdvOut", "FFFormatMimeType"); - const char *muxCustom = config_get_string(main->Config(), "AdvOut", "FFMCustom"); - const char *vEncoder = config_get_string(main->Config(), "AdvOut", "FFVEncoder"); - int vEncoderId = config_get_int(main->Config(), "AdvOut", "FFVEncoderId"); - const char *vEncCustom = config_get_string(main->Config(), "AdvOut", "FFVCustom"); - int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate"); - int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes"); - const char *aEncoder = config_get_string(main->Config(), "AdvOut", "FFAEncoder"); - int aEncoderId = config_get_int(main->Config(), "AdvOut", "FFAEncoderId"); - const char *aEncCustom = config_get_string(main->Config(), "AdvOut", "FFACustom"); - - OBSDataArrayAutoRelease audio_names = obs_data_array_create(); - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - - const char *audioName = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - OBSDataAutoRelease item = obs_data_create(); - obs_data_set_string(item, "name", audioName); - obs_data_array_push_back(audio_names, item); - } - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_array(settings, "audio_names", audio_names); - obs_data_set_string(settings, "url", url); - obs_data_set_string(settings, "format_name", formatName); - obs_data_set_string(settings, "format_mime_type", mimeType); - obs_data_set_string(settings, "muxer_settings", muxCustom); - obs_data_set_int(settings, "gop_size", gopSize); - obs_data_set_int(settings, "video_bitrate", vBitrate); - obs_data_set_string(settings, "video_encoder", vEncoder); - obs_data_set_int(settings, "video_encoder_id", vEncoderId); - obs_data_set_string(settings, "video_settings", vEncCustom); - obs_data_set_int(settings, "audio_bitrate", aBitrate); - obs_data_set_string(settings, "audio_encoder", aEncoder); - obs_data_set_int(settings, "audio_encoder_id", aEncoderId); - obs_data_set_string(settings, "audio_settings", aEncCustom); - - if (rescale && rescaleRes && *rescaleRes) { - int width; - int height; - int val = sscanf(rescaleRes, "%dx%d", &width, &height); - - if (val == 2 && width && height) { - obs_data_set_int(settings, "scale_width", width); - obs_data_set_int(settings, "scale_height", height); - } - } - - obs_output_set_mixers(fileOutput, aMixes); - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - obs_output_update(fileOutput, settings); -} - -static inline void SetEncoderName(obs_encoder_t *encoder, const char *name, const char *defaultName) -{ - obs_encoder_set_name(encoder, (name && *name) ? name : defaultName); -} - -inline void AdvancedOutput::UpdateAudioSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - const char *audioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - - bool is_multitrack_output = allowsMultiTrack(); - - OBSDataAutoRelease settings[MAX_AUDIO_MIXES]; - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - const char *name = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - string def_name = "Track"; - def_name += to_string((int)i + 1); - SetEncoderName(recordTrack[i], name, def_name.c_str()); - SetEncoderName(streamTrack[i], name, def_name.c_str()); - } - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - int track = (int)(i + 1); - settings[i] = obs_data_create(); - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, recAudioEncoder)); - - obs_encoder_update(recordTrack[i], settings[i]); - - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, audioEncoder)); - - if (!is_multitrack_output) { - if (track == streamTrackIndex || track == vodTrackIndex) { - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings[i], "bitrate"); - obs_service_apply_encoder_settings(main->GetService(), nullptr, settings[i]); - - if (!enforceBitrate) - obs_data_set_int(settings[i], "bitrate", bitrate); - } - } - - if (track == streamTrackIndex) - obs_encoder_update(streamAudioEnc, settings[i]); - if (track == vodTrackIndex) - obs_encoder_update(streamArchiveEnc, settings[i]); - } else { - obs_encoder_update(streamTrack[i], settings[i]); - } - } -} - -void AdvancedOutput::SetupOutputs() -{ - obs_encoder_set_video(videoStreaming, obs_get_video()); - if (videoRecording) - obs_encoder_set_video(videoRecording, obs_get_video()); - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - obs_encoder_set_audio(streamTrack[i], obs_get_audio()); - obs_encoder_set_audio(recordTrack[i], obs_get_audio()); - } - obs_encoder_set_audio(streamAudioEnc, obs_get_audio()); - obs_encoder_set_audio(streamArchiveEnc, obs_get_audio()); - - SetupStreaming(); - - if (ffmpegOutput) - SetupFFmpeg(); - else - SetupRecording(); -} - -int AdvancedOutput::GetAudioBitrate(size_t i, const char *id) const -{ - static const char *names[] = { - "Track1Bitrate", "Track2Bitrate", "Track3Bitrate", "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", - }; - int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]); - return FindClosestAvailableAudioBitrate(id, bitrate); -} - -inline std::optional AdvancedOutput::VodTrackMixerIdx(obs_service_t *service) -{ - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - bool vodTrackEnabled = config_get_bool(main->Config(), "AdvOut", "VodTrackEnabled"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) { - vodTrackEnabled = enableForCustomServer ? vodTrackEnabled : false; - } else { - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *service = obs_data_get_string(settings, "service"); - if (!ServiceSupportsVodTrack(service)) - vodTrackEnabled = false; - } - - if (vodTrackEnabled && streamTrackIndex != vodTrackIndex) - return {vodTrackIndex - 1}; - return std::nullopt; -} - -inline void AdvancedOutput::SetupVodTrack(obs_service_t *service) -{ - if (VodTrackMixerIdx(service).has_value()) - obs_output_set_audio_encoder(streamOutput, streamArchiveEnc, 1); - else - clear_archive_encoder(streamOutput, ADV_ARCHIVE_NAME); -} - -std::shared_future AdvancedOutput::SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) -{ - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - - bool is_multitrack_output = allowsMultiTrack(); - - if (!useStreamEncoder || (!ffmpegOutput && !obs_output_active(fileOutput))) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - const char *audio_encoder_id = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "adv_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - int idx = 0; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - return true; - }; - - return SetupMultitrackVideo(service, audio_encoder_id, static_cast(streamTrackIndex), - VodTrackMixerIdx(service), [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -bool AdvancedOutput::StartStreaming(obs_service_t *service) -{ - obs_output_set_service(streamOutput, service); - - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_int(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_int(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - bool is_rtmp = false; - obs_service_t *service_obj = main->GetService(); - const char *protocol = obs_service_get_protocol(service_obj); - if (protocol) { - if (astrcmpi_n(protocol, RTMP_PROTOCOL, strlen(RTMP_PROTOCOL)) == 0) - is_rtmp = true; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - if (is_rtmp) { - SetupVodTrack(service); - } - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -bool AdvancedOutput::StartRecording() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - bool splitFile; - const char *splitFileType; - int splitFileTime; - int splitFileSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) { - UpdateRecordingSettings(); - } - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - splitFile = config_get_bool(main->Config(), "AdvOut", "RecSplitFile"); - - string strPath = GetRecordingFilename(path, recFormat, noSpace, overwriteIfExists, filenameFormat, - ffmpegRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, ffmpegRecording ? "url" : "path", strPath.c_str()); - - if (splitFile) { - splitFileType = config_get_string(main->Config(), "AdvOut", "RecSplitFileType"); - splitFileTime = (astrcmpi(splitFileType, "Time") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileTime") - : 0; - splitFileSize = (astrcmpi(splitFileType, "Size") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileSize") - : 0; - string ext = GetFormatExt(recFormat); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", filenameFormat); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_bool(settings, "allow_overwrite", overwriteIfExists); - obs_data_set_bool(settings, "split_file", true); - obs_data_set_int(settings, "max_time_sec", splitFileTime * 60); - obs_data_set_int(settings, "max_size_mb", splitFileSize); - } - - obs_output_update(fileOutput, settings); - } - - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool AdvancedOutput::StartReplayBuffer() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - const char *rbPrefix; - const char *rbSuffix; - int rbTime; - int rbSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - rbTime = config_get_int(main->Config(), "AdvOut", "RecRBTime"); - rbSize = config_get_int(main->Config(), "AdvOut", "RecRBSize"); - - string f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(recFormat); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usesBitrate ? 0 : rbSize); - - obs_output_update(replayBuffer, settings); - } - - if (!obs_output_start(replayBuffer)) { - QString error_reason; - const char *error = obs_output_get_last_error(replayBuffer); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), error_reason); - return false; - } - - return true; -} - -void AdvancedOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void AdvancedOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void AdvancedOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool AdvancedOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool AdvancedOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool AdvancedOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - -void BasicOutputHandler::SetupAutoRemux(const char *&container) -{ - bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux"); - if (autoRemux && strcmp(container, "mp4") == 0) - container = "mkv"; -} - -std::string BasicOutputHandler::GetRecordingFilename(const char *path, const char *container, bool noSpace, - bool overwrite, const char *format, bool ffmpeg) -{ - if (!ffmpeg) - SetupAutoRemux(container); - - string dst = GetOutputFilename(path, container, noSpace, overwrite, format); - lastRecordingPath = dst; - return dst; -} - -extern std::string DeserializeConfigText(const char *text); - -std::shared_future BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service, std::string audio_encoder_id, - size_t main_audio_mixer, - std::optional vod_track_mixer, - std::function)> continuation) -{ - auto start_streaming_guard = std::make_shared(); - if (!multitrackVideo) { - continuation(std::nullopt); - return start_streaming_guard->GetFuture(); - } - - multitrackVideoActive = false; - - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - bool is_custom = strncmp("rtmp_custom", obs_service_get_type(service), 11) == 0; - - std::optional custom_config = std::nullopt; - if (config_get_bool(main->Config(), "Stream1", "MultitrackVideoConfigOverrideEnabled")) - custom_config = DeserializeConfigText( - config_get_string(main->Config(), "Stream1", "MultitrackVideoConfigOverride")); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - QString key = obs_data_get_string(settings, "key"); - - const char *service_name = ""; - if (is_custom && obs_data_has_user_value(settings, "service_name")) { - service_name = obs_data_get_string(settings, "service_name"); - } else if (!is_custom) { - service_name = obs_data_get_string(settings, "service"); - } - - std::optional custom_rtmp_url; - std::optional use_rtmps; - auto server = obs_data_get_string(settings, "server"); - if (strncmp(server, "auto", 4) != 0) { - custom_rtmp_url = server; - } else { - QString server_ = server; - use_rtmps = server_.contains("rtmps", Qt::CaseInsensitive); - } - - auto service_custom_server = obs_data_get_bool(settings, "using_custom_server"); - if (custom_rtmp_url.has_value()) { - blog(LOG_INFO, "Using %sserver '%s'", service_custom_server ? "custom " : "", custom_rtmp_url->c_str()); - } - - auto maximum_aggregate_bitrate = - config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto") - ? std::nullopt - : std::make_optional( - config_get_int(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrate")); - - auto maximum_video_tracks = config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracksAuto") - ? std::nullopt - : std::make_optional(config_get_int( - main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracks")); - - auto stream_dump_config = GenerateMultitrackVideoStreamDumpConfig(); - - auto continue_on_main_thread = [&, start_streaming_guard, service = OBSService{service}, - continuation = - std::move(continuation)](std::optional error) { - if (error) { - OBSDataAutoRelease service_settings = obs_service_get_settings(service); - auto multitrack_video_name = QTStr("Basic.Settings.Stream.MultitrackVideoLabel"); - if (obs_data_has_user_value(service_settings, "multitrack_video_name")) { - multitrack_video_name = obs_data_get_string(service_settings, "multitrack_video_name"); - } - - multitrackVideoActive = false; - if (!error->ShowDialog(main, multitrack_video_name)) - return continuation(false); - return continuation(std::nullopt); - } - - multitrackVideoActive = true; - - auto signal_handler = multitrackVideo->StreamingSignalHandler(); - - streamDelayStarting.Connect(signal_handler, "starting", OBSStreamStarting, this); - streamStopping.Connect(signal_handler, "stopping", OBSStreamStopping, this); - - startStreaming.Connect(signal_handler, "start", OBSStartStreaming, this); - stopStreaming.Connect(signal_handler, "stop", OBSStopStreaming, this); - return continuation(true); - }; - - QThreadPool::globalInstance()->start([=, multitrackVideo = multitrackVideo.get(), - service_name = std::string{service_name}, service = OBSService{service}, - stream_dump_config = OBSData{stream_dump_config}, - start_streaming_guard = start_streaming_guard]() mutable { - std::optional error; - try { - multitrackVideo->PrepareStreaming(main, service_name.c_str(), service, custom_rtmp_url, key, - audio_encoder_id.c_str(), maximum_aggregate_bitrate, - maximum_video_tracks, custom_config, stream_dump_config, - main_audio_mixer, vod_track_mixer, use_rtmps); - } catch (const MultitrackVideoError &error_) { - error.emplace(error_); - } - - QMetaObject::invokeMethod(main, [=] { continue_on_main_thread(error); }); - }); - - return start_streaming_guard->GetFuture(); -} - -OBSDataAutoRelease BasicOutputHandler::GenerateMultitrackVideoStreamDumpConfig() -{ - auto stream_dump_enabled = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpEnabled"); - - if (!stream_dump_enabled) - return nullptr; - - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - bool useMP4 = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpAsMP4"); - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, useMP4 ? "mp4" : "flv", noSpace, overwriteIfExists, f.c_str(), - // never remux stream dump - false); - obs_data_set_string(settings, "path", strPath.c_str()); - - if (useMP4) { - obs_data_set_bool(settings, "use_mp4", true); - obs_data_set_string(settings, "muxer_settings", "write_encoder_info=1"); - } - - return settings; -} - -BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main) -{ - return new SimpleOutput(main); -} - -BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main) -{ - return new AdvancedOutput(main); -} diff --git a/frontend/utility/SimpleOutput.hpp b/frontend/utility/SimpleOutput.hpp index 30d203174..46d6fc381 100644 --- a/frontend/utility/SimpleOutput.hpp +++ b/frontend/utility/SimpleOutput.hpp @@ -1,456 +1,6 @@ -#include -#include -#include -#include -#include -#include -#include "audio-encoders.hpp" -#include "multitrack-video-error.hpp" -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" -#include "window-basic-vcam.hpp" +#pragma once -using namespace std; - -extern bool EncoderAvailable(const char *encoder); - -volatile bool streaming_active = false; -volatile bool recording_active = false; -volatile bool recording_paused = false; -volatile bool replaybuf_active = false; -volatile bool virtualcam_active = false; - -#define RTMP_PROTOCOL "rtmp" -#define SRT_PROTOCOL "srt" -#define RIST_PROTOCOL "rist" - -static void OBSStreamStarting(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - return; - - output->delayActive = true; - QMetaObject::invokeMethod(output->main, "StreamDelayStarting", Q_ARG(int, sec)); -} - -static void OBSStreamStopping(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - QMetaObject::invokeMethod(output->main, "StreamStopping"); - else - QMetaObject::invokeMethod(output->main, "StreamDelayStopping", Q_ARG(int, sec)); -} - -static void OBSStartStreaming(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->streamingActive = true; - os_atomic_set_bool(&streaming_active, true); - QMetaObject::invokeMethod(output->main, "StreamingStart"); -} - -static void OBSStopStreaming(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->streamingActive = false; - output->delayActive = false; - output->multitrackVideoActive = false; - os_atomic_set_bool(&streaming_active, false); - QMetaObject::invokeMethod(output->main, "StreamingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSStartRecording(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->recordingActive = true; - os_atomic_set_bool(&recording_active, true); - QMetaObject::invokeMethod(output->main, "RecordingStart"); -} - -static void OBSStopRecording(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->recordingActive = false; - os_atomic_set_bool(&recording_active, false); - os_atomic_set_bool(&recording_paused, false); - QMetaObject::invokeMethod(output->main, "RecordingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSRecordStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "RecordStopping"); -} - -static void OBSRecordFileChanged(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - const char *next_file = calldata_string(params, "next_file"); - - QString arg_last_file = QString::fromUtf8(output->lastRecordingPath.c_str()); - - QMetaObject::invokeMethod(output->main, "RecordingFileChanged", Q_ARG(QString, arg_last_file)); - - output->lastRecordingPath = next_file; -} - -static void OBSStartReplayBuffer(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->replayBufferActive = true; - os_atomic_set_bool(&replaybuf_active, true); - QMetaObject::invokeMethod(output->main, "ReplayBufferStart"); -} - -static void OBSStopReplayBuffer(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->replayBufferActive = false; - os_atomic_set_bool(&replaybuf_active, false); - QMetaObject::invokeMethod(output->main, "ReplayBufferStop", Q_ARG(int, code)); -} - -static void OBSReplayBufferStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferStopping"); -} - -static void OBSReplayBufferSaved(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferSaved", Qt::QueuedConnection); -} - -static void OBSStartVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->virtualCamActive = true; - os_atomic_set_bool(&virtualcam_active, true); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStart"); -} - -static void OBSStopVirtualCam(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->virtualCamActive = false; - os_atomic_set_bool(&virtualcam_active, false); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", Q_ARG(int, code)); -} - -static void OBSDeactivateVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->DestroyVirtualCamView(); -} - -/* ------------------------------------------------------------------------ */ - -struct StartMultitrackVideoStreamingGuard { - StartMultitrackVideoStreamingGuard() { future = guard.get_future().share(); }; - ~StartMultitrackVideoStreamingGuard() { guard.set_value(); } - - std::shared_future GetFuture() const { return future; } - - static std::shared_future MakeReadyFuture() - { - StartMultitrackVideoStreamingGuard guard; - return guard.GetFuture(); - } - -private: - std::promise guard; - std::shared_future future; -}; - -/* ------------------------------------------------------------------------ */ - -static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleAACEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static bool CreateSimpleOpusEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleOpusEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, - const char *prot_test2 = nullptr) -{ - return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && - (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; -} - -static bool return_first_id(void *data, const char *id) -{ - const char **output = (const char **)data; - - *output = id; - return false; -} - -static const char *GetStreamOutputType(const obs_service_t *service) -{ - const char *protocol = obs_service_get_protocol(service); - const char *output = nullptr; - - if (!protocol) { - blog(LOG_WARNING, "The service '%s' has no protocol set", obs_service_get_id(service)); - return nullptr; - } - - if (!obs_is_output_protocol_registered(protocol)) { - blog(LOG_WARNING, "The protocol '%s' is not registered", protocol); - return nullptr; - } - - /* Check if the service has a preferred output type */ - output = obs_service_get_preferred_output_type(service); - if (output) { - if ((obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0) - return output; - - blog(LOG_WARNING, "The output '%s' is not registered, fallback to another one", output); - } - - /* Otherwise, prefer first-party output types */ - if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) { - return "rtmp_output"; - } else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) { - return "ffmpeg_hls_muxer"; - } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", "RIST")) { - return "ffmpeg_mpegts_muxer"; - } - - /* If third-party protocol, use the first enumerated type */ - obs_enum_output_types_with_protocol(protocol, &output, return_first_id); - if (output) - return output; - - blog(LOG_WARNING, "No output compatible with the service '%s' is registered", obs_service_get_id(service)); - - return nullptr; -} - -/* ------------------------------------------------------------------------ */ - -inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) -{ - if (main->vcamEnabled) { - virtualCam = obs_output_create(VIRTUAL_CAM_ID, "virtualcam_output", nullptr, nullptr); - - signal_handler_t *signal = obs_output_get_signal_handler(virtualCam); - startVirtualCam.Connect(signal, "start", OBSStartVirtualCam, this); - stopVirtualCam.Connect(signal, "stop", OBSStopVirtualCam, this); - deactivateVirtualCam.Connect(signal, "deactivate", OBSDeactivateVirtualCam, this); - } - - auto multitrack_enabled = config_get_bool(main->Config(), "Stream1", "EnableMultitrackVideo"); - if (!config_has_user_value(main->Config(), "Stream1", "EnableMultitrackVideo")) { - auto service = main_->GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - multitrack_enabled = obs_data_has_user_value(settings, "multitrack_video_configuration_url"); - } - if (multitrack_enabled) - multitrackVideo = make_unique(); -} - -extern void log_vcam_changed(const VCamConfig &config, bool starting); - -bool BasicOutputHandler::StartVirtualCam() -{ - if (!main->vcamEnabled) - return false; - - bool typeIsProgram = main->vcamConfig.type == VCamOutputType::ProgramView; - - if (!virtualCamView && !typeIsProgram) - virtualCamView = obs_view_create(); - - UpdateVirtualCamOutputSource(); - - if (!virtualCamVideo) { - virtualCamVideo = typeIsProgram ? obs_get_video() : obs_view_add(virtualCamView); - - if (!virtualCamVideo) - return false; - } - - obs_output_set_media(virtualCam, virtualCamVideo, obs_get_audio()); - if (!Active()) - SetupOutputs(); - - bool success = obs_output_start(virtualCam); - if (!success) { - QString errorReason; - - const char *error = obs_output_get_last_error(virtualCam); - if (error) { - errorReason = QT_UTF8(error); - } else { - errorReason = QTStr("Output.StartFailedGeneric"); - } - - QMessageBox::critical(main, QTStr("Output.StartVirtualCamFailed"), errorReason); - - DestroyVirtualCamView(); - } - - log_vcam_changed(main->vcamConfig, true); - - return success; -} - -void BasicOutputHandler::StopVirtualCam() -{ - if (main->vcamEnabled) { - obs_output_stop(virtualCam); - } -} - -bool BasicOutputHandler::VirtualCamActive() const -{ - if (main->vcamEnabled) { - return obs_output_active(virtualCam); - } - return false; -} - -void BasicOutputHandler::UpdateVirtualCamOutputSource() -{ - if (!main->vcamEnabled || !virtualCamView) - return; - - OBSSourceAutoRelease source; - - switch (main->vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - DestroyVirtualCameraScene(); - return; - case VCamOutputType::PreviewOutput: { - DestroyVirtualCameraScene(); - OBSSource s = main->GetCurrentSceneSource(); - obs_source_get_ref(s); - source = s.Get(); - break; - } - case VCamOutputType::SceneOutput: - DestroyVirtualCameraScene(); - source = obs_get_source_by_name(main->vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - OBSSourceAutoRelease s = obs_get_source_by_name(main->vcamConfig.source.c_str()); - - if (!vCamSourceScene) - vCamSourceScene = obs_scene_create_private("vcam_source"); - source = obs_source_get_ref(obs_scene_get_source(vCamSourceScene)); - - if (vCamSourceSceneItem && (obs_sceneitem_get_source(vCamSourceSceneItem) != s)) { - obs_sceneitem_remove(vCamSourceSceneItem); - vCamSourceSceneItem = nullptr; - } - - if (!vCamSourceSceneItem) { - vCamSourceSceneItem = obs_scene_add(vCamSourceScene, s); - - obs_sceneitem_set_bounds_type(vCamSourceSceneItem, OBS_BOUNDS_SCALE_INNER); - obs_sceneitem_set_bounds_alignment(vCamSourceSceneItem, OBS_ALIGN_CENTER); - - const struct vec2 size = { - (float)obs_source_get_width(source), - (float)obs_source_get_height(source), - }; - obs_sceneitem_set_bounds(vCamSourceSceneItem, &size); - } - break; - } - - OBSSourceAutoRelease current = obs_view_get_source(virtualCamView, 0); - if (source != current) - obs_view_set_source(virtualCamView, 0, source); -} - -void BasicOutputHandler::DestroyVirtualCamView() -{ - if (main->vcamConfig.type == VCamOutputType::ProgramView) { - virtualCamVideo = nullptr; - return; - } - - obs_view_remove(virtualCamView); - obs_view_set_source(virtualCamView, 0, nullptr); - virtualCamVideo = nullptr; - - obs_view_destroy(virtualCamView); - virtualCamView = nullptr; - - DestroyVirtualCameraScene(); -} - -void BasicOutputHandler::DestroyVirtualCameraScene() -{ - if (!vCamSourceScene) - return; - - obs_scene_release(vCamSourceScene); - vCamSourceScene = nullptr; - vCamSourceSceneItem = nullptr; -} - -/* ------------------------------------------------------------------------ */ +#include "BasicOutputHandler.hpp" struct SimpleOutput : BasicOutputHandler { OBSEncoder audioStreaming; @@ -460,8 +10,8 @@ struct SimpleOutput : BasicOutputHandler { OBSEncoder videoRecording; OBSEncoder audioTrack[MAX_AUDIO_MIXES]; - string videoEncoder; - string videoQuality; + std::string videoEncoder; + std::string videoQuality; bool usingRecordingPreset = false; bool recordingConfigured = false; bool ffmpegOutput = false; @@ -511,2031 +61,3 @@ struct SimpleOutput : BasicOutputHandler { virtual bool RecordingActive() const override; virtual bool ReplayBufferActive() const override; }; - -void SimpleOutput::LoadRecordingPreset_Lossless() -{ - fileOutput = obs_output_create("ffmpeg_output", "simple_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(simple output)"; - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "format_name", "avi"); - obs_data_set_string(settings, "video_encoder", "utvideo"); - obs_data_set_string(settings, "audio_encoder", "pcm_s16le"); - - obs_output_update(fileOutput, settings); -} - -void SimpleOutput::LoadRecordingPreset_Lossy(const char *encoderId) -{ - videoRecording = obs_video_encoder_create(encoderId, "simple_video_recording", nullptr, nullptr); - if (!videoRecording) - throw "Failed to create video recording encoder (simple output)"; - obs_encoder_release(videoRecording); -} - -void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId) -{ - videoStreaming = obs_video_encoder_create(encoderId, "simple_video_stream", nullptr, nullptr); - if (!videoStreaming) - throw "Failed to create video streaming encoder (simple output)"; - obs_encoder_release(videoStreaming); -} - -/* mistakes have been made to lead us to this. */ -const char *get_simple_output_encoder(const char *encoder) -{ - if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - return "obs_qsv11_v2"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - return "obs_qsv11_av1"; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - return "h264_texture_amf"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - return "h265_texture_amf"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - return "av1_texture_amf"; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - return EncoderAvailable("obs_nvenc_h264_tex") ? "obs_nvenc_h264_tex" : "ffmpeg_nvenc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - return EncoderAvailable("obs_nvenc_hevc_tex") ? "obs_nvenc_hevc_tex" : "ffmpeg_hevc_nvenc"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - return "obs_nvenc_av1_tex"; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.avc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.hevc"; -#endif - } - - return "obs_x264"; -} - -void SimpleOutput::LoadRecordingPreset() -{ - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "RecEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "RecAudioEncoder"); - - videoEncoder = encoder; - videoQuality = quality; - ffmpegOutput = false; - - if (strcmp(quality, "Stream") == 0) { - videoRecording = videoStreaming; - audioRecording = audioStreaming; - usingRecordingPreset = false; - return; - - } else if (strcmp(quality, "Lossless") == 0) { - LoadRecordingPreset_Lossless(); - usingRecordingPreset = true; - ffmpegOutput = true; - return; - - } else { - lowCPUx264 = false; - - if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) - lowCPUx264 = true; - LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder)); - usingRecordingPreset = true; - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioRecording, 192, "simple_opus_recording", 0); - else - success = CreateSimpleAACEncoder(audioRecording, 192, "simple_aac_recording", 0); - - if (!success) - throw "Failed to create audio recording encoder " - "(simple output)"; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[23]; - if (strcmp(audio_encoder, "opus") == 0) { - snprintf(name, sizeof name, "simple_opus_recording%d", i); - success = CreateSimpleOpusEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } else { - snprintf(name, sizeof name, "simple_aac_recording%d", i); - success = CreateSimpleAACEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } - if (!success) - throw "Failed to create multi-track audio recording encoder " - "(simple output)"; - } - } -} - -#define SIMPLE_ARCHIVE_NAME "simple_archive_audio" - -SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - - LoadStreamingPreset_Lossy(get_simple_output_encoder(encoder)); - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioStreaming, GetAudioBitrate(), "simple_opus", 0); - else - success = CreateSimpleAACEncoder(audioStreaming, GetAudioBitrate(), "simple_aac", 0); - - if (!success) - throw "Failed to create audio streaming encoder (simple output)"; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - else - success = CreateSimpleAACEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - - if (!success) - throw "Failed to create audio archive encoder (simple output)"; - - LoadRecordingPreset(); - - if (!ffmpegOutput) { - bool useReplayBuffer = config_get_bool(main->Config(), "SimpleOutput", "RecRB"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool use_native = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(use_native ? "mp4_output" : "ffmpeg_muxer", "simple_file_output", - nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(simple output)"; - } - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); -} - -int SimpleOutput::GetAudioBitrate() const -{ - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); - - if (strcmp(audio_encoder, "opus") == 0) - return FindClosestAvailableSimpleOpusBitrate(bitrate); - - return FindClosestAvailableSimpleAACBitrate(bitrate); -} - -void SimpleOutput::Update() -{ - OBSDataAutoRelease videoSettings = obs_data_create(); - OBSDataAutoRelease audioSettings = obs_data_create(); - - int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); - int audioBitrate = GetAudioBitrate(); - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - const char *custom = config_get_string(main->Config(), "SimpleOutput", "x264Settings"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *encoder_id = obs_encoder_get_id(videoStreaming); - const char *presetType; - const char *preset; - - if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - presetType = "AMDPreset"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset2"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - presetType = "NVENCPreset2"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - presetType = "AMDAV1Preset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - presetType = "NVENCPreset2"; - - } else { - presetType = "Preset"; - } - - preset = config_get_string(main->Config(), "SimpleOutput", presetType); - - /* Only use preset2 for legacy/FFmpeg NVENC Encoder. */ - if (strncmp(encoder_id, "ffmpeg_", 7) == 0 && strcmp(presetType, "NVENCPreset2") == 0) { - obs_data_set_string(videoSettings, "preset2", preset); - } else { - obs_data_set_string(videoSettings, "preset", preset); - } - - obs_data_set_string(videoSettings, "rate_control", "CBR"); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - - if (advanced) - obs_data_set_string(videoSettings, "x264opts", custom); - - obs_data_set_string(audioSettings, "rate_control", "CBR"); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - - obs_service_apply_encoder_settings(main->GetService(), videoSettings, audioSettings); - - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - } - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, videoSettings); - obs_encoder_update(audioStreaming, audioSettings); - obs_encoder_update(audioArchive, audioSettings); -} - -void SimpleOutput::UpdateRecordingAudioSettings() -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", 192); - obs_data_set_string(settings, "rate_control", "CBR"); - - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv || strcmp(quality, "Stream") == 0) { - obs_encoder_update(audioRecording, settings); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_update(audioTrack[i], settings); - } - } - } -} - -#define CROSS_DIST_CUTOFF 2000.0 - -int SimpleOutput::CalcCRF(int crf) -{ - int cx = config_get_uint(main->Config(), "Video", "OutputCX"); - int cy = config_get_uint(main->Config(), "Video", "OutputCY"); - double fCX = double(cx); - double fCY = double(cy); - - if (lowCPUx264) - crf -= 2; - - double crossDist = sqrt(fCX * fCX + fCY * fCY); - double crfResReduction = fmin(CROSS_DIST_CUTOFF, crossDist) / CROSS_DIST_CUTOFF; - crfResReduction = (1.0 - crfResReduction) * 10.0; - - return crf - int(crfResReduction); -} - -void SimpleOutput::UpdateRecordingSettings_x264_crf(int crf) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "crf", crf); - obs_data_set_bool(settings, "use_bufsize", true); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", lowCPUx264 ? "ultrafast" : "veryfast"); - - obs_encoder_update(videoRecording, settings); -} - -static bool icq_available(obs_encoder_t *encoder) -{ - obs_properties_t *props = obs_encoder_properties(encoder); - obs_property_t *p = obs_properties_get(props, "rate_control"); - bool icq_found = false; - - size_t num = obs_property_list_item_count(p); - for (size_t i = 0; i < num; i++) { - const char *val = obs_property_list_item_string(p, i); - if (strcmp(val, "ICQ") == 0) { - icq_found = true; - break; - } - } - - obs_properties_destroy(props); - return icq_found; -} - -void SimpleOutput::UpdateRecordingSettings_qsv11(int crf, bool av1) -{ - bool icq = icq_available(videoRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "profile", "high"); - - if (icq && !av1) { - obs_data_set_string(settings, "rate_control", "ICQ"); - obs_data_set_int(settings, "icq_quality", crf); - } else { - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_int(settings, "cqp", crf); - } - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc_hevc_av1(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_apple(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} - -#ifdef ENABLE_HEVC -void SimpleOutput::UpdateRecordingSettings_apple_hevc(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} -#endif - -void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", "quality"); - obs_data_set_int(settings, "cqp", cqp); - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings() -{ - bool ultra_hq = (videoQuality == "HQ"); - int crf = CalcCRF(ultra_hq ? 16 : 23); - - if (astrcmp_n(videoEncoder.c_str(), "x264", 4) == 0) { - UpdateRecordingSettings_x264_crf(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV) { - UpdateRecordingSettings_qsv11(crf, false); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV_AV1) { - UpdateRecordingSettings_qsv11(crf, true); - - } else if (videoEncoder == SIMPLE_ENCODER_AMD) { - UpdateRecordingSettings_amd_cqp(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_AMD_HEVC) { - UpdateRecordingSettings_amd_cqp(crf); -#endif - - } else if (videoEncoder == SIMPLE_ENCODER_AMD_AV1) { - UpdateRecordingSettings_amd_cqp(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_NVENC) { - UpdateRecordingSettings_nvenc(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_HEVC) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); -#endif - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_AV1) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_H264) { - /* These are magic numbers. 0 - 100, more is better. */ - UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_HEVC) { - UpdateRecordingSettings_apple_hevc(ultra_hq ? 70 : 50); -#endif - } - UpdateRecordingAudioSettings(); -} - -inline void SimpleOutput::SetupOutputs() -{ - SimpleOutput::Update(); - obs_encoder_set_video(videoStreaming, obs_get_video()); - obs_encoder_set_audio(audioStreaming, obs_get_audio()); - obs_encoder_set_audio(audioArchive, obs_get_audio()); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (usingRecordingPreset) { - if (ffmpegOutput) { - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - } else { - obs_encoder_set_video(videoRecording, obs_get_video()); - if (flv) { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_set_audio(audioTrack[i], obs_get_audio()); - } - } - } - } - } else { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } -} - -const char *FindAudioEncoderFromCodec(const char *type) -{ - const char *alt_enc_id = nullptr; - size_t i = 0; - - while (obs_enum_encoder_types(i++, &alt_enc_id)) { - const char *codec = obs_get_encoder_codec(alt_enc_id); - if (strcmp(type, codec) == 0) { - return alt_enc_id; - } - } - - return nullptr; -} - -std::shared_future SimpleOutput::SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation) -{ - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - auto audio_bitrate = GetAudioBitrate(); - auto vod_track_mixer = IsVodTrackEnabled(service) ? std::optional{1} : std::nullopt; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "simple_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, audioStreaming, 0); - obs_output_set_service(streamOutput, service); - return true; - }; - - return SetupMultitrackVideo(service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0, vod_track_mixer, - [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -static inline bool ServiceSupportsVodTrack(const char *service); - -static void clear_archive_encoder(obs_output_t *output, const char *expected_name) -{ - obs_encoder_t *last = obs_output_get_audio_encoder(output, 1); - bool clear = false; - - /* ensures that we don't remove twitch's soundtrack encoder */ - if (last) { - const char *name = obs_encoder_get_name(last); - clear = name && strcmp(name, expected_name) == 0; - obs_encoder_release(last); - } - - if (clear) - obs_output_set_audio_encoder(output, nullptr, 1); -} - -bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service) -{ - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enable = config_get_bool(main->Config(), "SimpleOutput", "VodTrackEnabled"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *name = obs_data_get_string(settings, "service"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) - return enableForCustomServer ? enable : false; - else - return advanced && enable && ServiceSupportsVodTrack(name); -} - -void SimpleOutput::SetupVodTrack(obs_service_t *service) -{ - if (IsVodTrackEnabled(service)) - obs_output_set_audio_encoder(streamOutput, audioArchive, 1); - else - clear_archive_encoder(streamOutput, SIMPLE_ARCHIVE_NAME); -} - -bool SimpleOutput::StartStreaming(obs_service_t *service) -{ - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_uint(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_uint(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - - if (!multitrackVideo || !multitrackVideoActive) - SetupVodTrack(service); - - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -void SimpleOutput::UpdateRecording() -{ - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - int idx = 0; - int idx2 = 0; - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - - if (replayBufferActive || recordingActive) - return; - - if (usingRecordingPreset) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(streamOutput)) { - Update(); - } - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput) { - obs_output_set_video_encoder(fileOutput, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(fileOutput, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, audioTrack[i], idx++); - } - } - } - } - if (replayBuffer) { - obs_output_set_video_encoder(replayBuffer, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(replayBuffer, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(replayBuffer, audioTrack[i], idx2++); - } - } - } - } - - recordingConfigured = true; -} - -bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) -{ - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - const char *format = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *mux = config_get_string(main->Config(), "SimpleOutput", "MuxerCustom"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - int rbTime = config_get_int(main->Config(), "SimpleOutput", "RecRBTime"); - int rbSize = config_get_int(main->Config(), "SimpleOutput", "RecRBSize"); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - - bool is_fragmented = strncmp(format, "fragmented", 10) == 0; - bool is_lossless = videoQuality == "Lossless"; - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - if (updateReplayBuffer) { - f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(format); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usingRecordingPreset ? rbSize : 0); - } else { - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, ffmpegOutput ? "avi" : format, noSpace, overwriteIfExists, - f.c_str(), ffmpegOutput); - obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str()); - if (ffmpegOutput) - obs_output_set_mixers(fileOutput, tracks); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && !is_lossless && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented && !is_lossless) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - if (updateReplayBuffer) - obs_output_update(replayBuffer, settings); - else - obs_output_update(fileOutput, settings); - - return true; -} - -bool SimpleOutput::StartRecording() -{ - UpdateRecording(); - if (!ConfigureRecording(false)) - return false; - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool SimpleOutput::StartReplayBuffer() -{ - UpdateRecording(); - if (!ConfigureRecording(true)) - return false; - if (!obs_output_start(replayBuffer)) { - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), QTStr("Output.StartFailedGeneric")); - return false; - } - - return true; -} - -void SimpleOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void SimpleOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void SimpleOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool SimpleOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool SimpleOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool SimpleOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - -struct AdvancedOutput : BasicOutputHandler { - OBSEncoder streamAudioEnc; - OBSEncoder streamArchiveEnc; - OBSEncoder streamTrack[MAX_AUDIO_MIXES]; - OBSEncoder recordTrack[MAX_AUDIO_MIXES]; - OBSEncoder videoStreaming; - OBSEncoder videoRecording; - - bool ffmpegOutput; - bool ffmpegRecording; - bool useStreamEncoder; - bool useStreamAudioEncoder; - bool usesBitrate = false; - - AdvancedOutput(OBSBasic *main_); - - inline void UpdateStreamSettings(); - inline void UpdateRecordingSettings(); - inline void UpdateAudioSettings(); - virtual void Update() override; - - inline std::optional VodTrackMixerIdx(obs_service_t *service); - inline void SetupVodTrack(obs_service_t *service); - - inline void SetupStreaming(); - inline void SetupRecording(); - inline void SetupFFmpeg(); - void SetupOutputs() override; - int GetAudioBitrate(size_t i, const char *id) const; - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; - bool allowsMultiTrack(); -}; - -static OBSData GetDataFromJsonFile(const char *jsonFile) -{ - const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); - - const OBSProfile ¤tProfile = basic->GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(jsonFile); - - OBSDataAutoRelease data = nullptr; - - if (!jsonFilePath.empty()) { - BPtr jsonData = os_quick_read_utf8_file(jsonFilePath.u8string().c_str()); - - if (!!jsonData) { - data = obs_data_create_from_json(jsonData); - } - } - - if (!data) { - data = obs_data_create(); - } - - return data.Get(); -} - -static void ApplyEncoderDefaults(OBSData &settings, const obs_encoder_t *encoder) -{ - OBSData dataRet = obs_encoder_get_defaults(encoder); - obs_data_release(dataRet); - - if (!!settings) - obs_data_apply(dataRet, settings); - settings = std::move(dataRet); -} - -#define ADV_ARCHIVE_NAME "adv_archive_audio" - -#ifdef __APPLE__ -static void translate_macvth264_encoder(const char *&encoder) -{ - if (strcmp(encoder, "vt_h264_hw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264.gva"; - } else if (strcmp(encoder, "vt_h264_sw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264"; - } -} -#endif - -AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *recType = config_get_string(main->Config(), "AdvOut", "RecType"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - const char *streamAudioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recordEncoder = config_get_string(main->Config(), "AdvOut", "RecEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); -#ifdef __APPLE__ - translate_macvth264_encoder(streamEncoder); - translate_macvth264_encoder(recordEncoder); -#endif - - ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - ffmpegRecording = ffmpegOutput && config_get_bool(main->Config(), "AdvOut", "FFOutputToFile"); - useStreamEncoder = astrcmpi(recordEncoder, "none") == 0; - useStreamAudioEncoder = astrcmpi(recAudioEncoder, "none") == 0; - - OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); - OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); - - if (ffmpegOutput) { - fileOutput = obs_output_create("ffmpeg_output", "adv_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(advanced output)"; - } else { - bool useReplayBuffer = config_get_bool(main->Config(), "AdvOut", "RecRB"); - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool native_muxer = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(native_muxer ? "mp4_output" : "ffmpeg_muxer", "adv_file_output", nullptr, - nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(advanced output)"; - - if (!useStreamEncoder) { - videoRecording = obs_video_encoder_create(recordEncoder, "advanced_video_recording", - recordEncSettings, nullptr); - if (!videoRecording) - throw "Failed to create recording video " - "encoder (advanced output)"; - obs_encoder_release(videoRecording); - } - } - - videoStreaming = obs_video_encoder_create(streamEncoder, "advanced_video_stream", streamEncSettings, nullptr); - if (!videoStreaming) - throw "Failed to create streaming video encoder " - "(advanced output)"; - obs_encoder_release(videoStreaming); - - const char *rate_control = - obs_data_get_string(useStreamEncoder ? streamEncSettings : recordEncSettings, "rate_control"); - if (!rate_control) - rate_control = ""; - usesBitrate = astrcmpi(rate_control, "CBR") == 0 || astrcmpi(rate_control, "VBR") == 0 || - astrcmpi(rate_control, "ABR") == 0; - - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[19]; - snprintf(name, sizeof(name), "adv_record_audio_%d", i); - - recordTrack[i] = obs_audio_encoder_create(useStreamAudioEncoder ? streamAudioEncoder : recAudioEncoder, - name, nullptr, i, nullptr); - - if (!recordTrack[i]) { - throw "Failed to create audio encoder " - "(advanced output)"; - } - - obs_encoder_release(recordTrack[i]); - - snprintf(name, sizeof(name), "adv_stream_audio_%d", i); - streamTrack[i] = obs_audio_encoder_create(streamAudioEncoder, name, nullptr, i, nullptr); - - if (!streamTrack[i]) { - throw "Failed to create streaming audio encoders " - "(advanced output)"; - } - - obs_encoder_release(streamTrack[i]); - } - - std::string id; - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - streamAudioEnc = - obs_audio_encoder_create(streamAudioEncoder, "adv_stream_audio", nullptr, streamTrackIndex, nullptr); - if (!streamAudioEnc) - throw "Failed to create streaming audio encoder " - "(advanced output)"; - obs_encoder_release(streamAudioEnc); - - id = ""; - int vodTrack = config_get_int(main->Config(), "AdvOut", "VodTrackIndex") - 1; - streamArchiveEnc = obs_audio_encoder_create(streamAudioEncoder, ADV_ARCHIVE_NAME, nullptr, vodTrack, nullptr); - if (!streamArchiveEnc) - throw "Failed to create archive audio encoder " - "(advanced output)"; - obs_encoder_release(streamArchiveEnc); - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); - recordFileChanged.Connect(obs_output_get_signal_handler(fileOutput), "file_changed", OBSRecordFileChanged, - this); -} - -void AdvancedOutput::UpdateStreamSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - bool dynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - - OBSData settings = GetDataFromJsonFile("streamEncoder.json"); - ApplyEncoderDefaults(settings, videoStreaming); - - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings, "bitrate"); - int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(settings, "bitrate", bitrate); - } - - int enforced_keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - if (keyint_sec != 0 && keyint_sec < enforced_keyint_sec) - obs_data_set_int(settings, "keyint_sec", keyint_sec); - } else { - blog(LOG_WARNING, "User is ignoring service settings."); - } - - if (dynBitrate && strstr(streamEncoder, "nvenc") != nullptr) - obs_data_set_bool(settings, "lookahead", false); - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, settings); -} - -inline void AdvancedOutput::UpdateRecordingSettings() -{ - OBSData settings = GetDataFromJsonFile("recordEncoder.json"); - obs_encoder_update(videoRecording, settings); -} - -void AdvancedOutput::Update() -{ - UpdateStreamSettings(); - if (!useStreamEncoder && !ffmpegOutput) - UpdateRecordingSettings(); - UpdateAudioSettings(); -} - -static inline bool ServiceSupportsVodTrack(const char *service) -{ - static const char *vodTrackServices[] = {"Twitch"}; - - for (const char *vodTrackService : vodTrackServices) { - if (astrcmpi(vodTrackService, service) == 0) - return true; - } - - return false; -} - -inline bool AdvancedOutput::allowsMultiTrack() -{ - const char *protocol = nullptr; - obs_service_t *service_obj = main->GetService(); - protocol = obs_service_get_protocol(service_obj); - if (!protocol) - return false; - return astrcmpi_n(protocol, SRT_PROTOCOL, strlen(SRT_PROTOCOL)) == 0 || - astrcmpi_n(protocol, RIST_PROTOCOL, strlen(RIST_PROTOCOL)) == 0; -} - -inline void AdvancedOutput::SetupStreaming() -{ - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RescaleFilter"); - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - bool is_multitrack_output = allowsMultiTrack(); - - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - obs_encoder_set_scaled_size(videoStreaming, cx, cy); - obs_encoder_set_gpu_scale_type(videoStreaming, (obs_scale_type)rescaleFilter); - - const char *id = obs_service_get_id(main->GetService()); - if (strcmp(id, "rtmp_custom") == 0) { - OBSDataAutoRelease settings = obs_data_create(); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - obs_encoder_update(videoStreaming, settings); - } -} - -inline void AdvancedOutput::SetupRecording() -{ - const char *path = config_get_string(main->Config(), "AdvOut", "RecFilePath"); - const char *mux = config_get_string(main->Config(), "AdvOut", "RecMuxerCustom"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RecRescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RecRescaleFilter"); - int tracks; - - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); - - bool is_fragmented = strncmp(recFormat, "fragmented", 10) == 0; - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv) - tracks = config_get_int(main->Config(), "AdvOut", "FLVTrack"); - else - tracks = config_get_int(main->Config(), "AdvOut", "RecTracks"); - - OBSDataAutoRelease settings = obs_data_create(); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - - /* Hack to allow recordings without any audio tracks selected. It is no - * longer possible to select such a configuration in settings, but legacy - * configurations might still have this configured and we don't want to - * just break them. */ - if (tracks == 0) - tracks = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - - if (useStreamEncoder) { - obs_output_set_video_encoder(fileOutput, videoStreaming); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoStreaming); - } else { - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - obs_encoder_set_scaled_size(videoRecording, cx, cy); - obs_encoder_set_gpu_scale_type(videoRecording, (obs_scale_type)rescaleFilter); - obs_output_set_video_encoder(fileOutput, videoRecording); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoRecording); - } - - if (!flv) { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[i], idx); - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[i], idx); - idx++; - } - } - } else if (flv && tracks != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[tracks - 1], idx); - - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[tracks - 1], idx); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - obs_data_set_string(settings, "path", path); - obs_output_update(fileOutput, settings); - if (replayBuffer) - obs_output_update(replayBuffer, settings); -} - -inline void AdvancedOutput::SetupFFmpeg() -{ - const char *url = config_get_string(main->Config(), "AdvOut", "FFURL"); - int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate"); - int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize"); - bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "FFRescaleRes"); - const char *formatName = config_get_string(main->Config(), "AdvOut", "FFFormat"); - const char *mimeType = config_get_string(main->Config(), "AdvOut", "FFFormatMimeType"); - const char *muxCustom = config_get_string(main->Config(), "AdvOut", "FFMCustom"); - const char *vEncoder = config_get_string(main->Config(), "AdvOut", "FFVEncoder"); - int vEncoderId = config_get_int(main->Config(), "AdvOut", "FFVEncoderId"); - const char *vEncCustom = config_get_string(main->Config(), "AdvOut", "FFVCustom"); - int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate"); - int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes"); - const char *aEncoder = config_get_string(main->Config(), "AdvOut", "FFAEncoder"); - int aEncoderId = config_get_int(main->Config(), "AdvOut", "FFAEncoderId"); - const char *aEncCustom = config_get_string(main->Config(), "AdvOut", "FFACustom"); - - OBSDataArrayAutoRelease audio_names = obs_data_array_create(); - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - - const char *audioName = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - OBSDataAutoRelease item = obs_data_create(); - obs_data_set_string(item, "name", audioName); - obs_data_array_push_back(audio_names, item); - } - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_array(settings, "audio_names", audio_names); - obs_data_set_string(settings, "url", url); - obs_data_set_string(settings, "format_name", formatName); - obs_data_set_string(settings, "format_mime_type", mimeType); - obs_data_set_string(settings, "muxer_settings", muxCustom); - obs_data_set_int(settings, "gop_size", gopSize); - obs_data_set_int(settings, "video_bitrate", vBitrate); - obs_data_set_string(settings, "video_encoder", vEncoder); - obs_data_set_int(settings, "video_encoder_id", vEncoderId); - obs_data_set_string(settings, "video_settings", vEncCustom); - obs_data_set_int(settings, "audio_bitrate", aBitrate); - obs_data_set_string(settings, "audio_encoder", aEncoder); - obs_data_set_int(settings, "audio_encoder_id", aEncoderId); - obs_data_set_string(settings, "audio_settings", aEncCustom); - - if (rescale && rescaleRes && *rescaleRes) { - int width; - int height; - int val = sscanf(rescaleRes, "%dx%d", &width, &height); - - if (val == 2 && width && height) { - obs_data_set_int(settings, "scale_width", width); - obs_data_set_int(settings, "scale_height", height); - } - } - - obs_output_set_mixers(fileOutput, aMixes); - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - obs_output_update(fileOutput, settings); -} - -static inline void SetEncoderName(obs_encoder_t *encoder, const char *name, const char *defaultName) -{ - obs_encoder_set_name(encoder, (name && *name) ? name : defaultName); -} - -inline void AdvancedOutput::UpdateAudioSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - const char *audioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - - bool is_multitrack_output = allowsMultiTrack(); - - OBSDataAutoRelease settings[MAX_AUDIO_MIXES]; - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - const char *name = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - string def_name = "Track"; - def_name += to_string((int)i + 1); - SetEncoderName(recordTrack[i], name, def_name.c_str()); - SetEncoderName(streamTrack[i], name, def_name.c_str()); - } - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - int track = (int)(i + 1); - settings[i] = obs_data_create(); - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, recAudioEncoder)); - - obs_encoder_update(recordTrack[i], settings[i]); - - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, audioEncoder)); - - if (!is_multitrack_output) { - if (track == streamTrackIndex || track == vodTrackIndex) { - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings[i], "bitrate"); - obs_service_apply_encoder_settings(main->GetService(), nullptr, settings[i]); - - if (!enforceBitrate) - obs_data_set_int(settings[i], "bitrate", bitrate); - } - } - - if (track == streamTrackIndex) - obs_encoder_update(streamAudioEnc, settings[i]); - if (track == vodTrackIndex) - obs_encoder_update(streamArchiveEnc, settings[i]); - } else { - obs_encoder_update(streamTrack[i], settings[i]); - } - } -} - -void AdvancedOutput::SetupOutputs() -{ - obs_encoder_set_video(videoStreaming, obs_get_video()); - if (videoRecording) - obs_encoder_set_video(videoRecording, obs_get_video()); - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - obs_encoder_set_audio(streamTrack[i], obs_get_audio()); - obs_encoder_set_audio(recordTrack[i], obs_get_audio()); - } - obs_encoder_set_audio(streamAudioEnc, obs_get_audio()); - obs_encoder_set_audio(streamArchiveEnc, obs_get_audio()); - - SetupStreaming(); - - if (ffmpegOutput) - SetupFFmpeg(); - else - SetupRecording(); -} - -int AdvancedOutput::GetAudioBitrate(size_t i, const char *id) const -{ - static const char *names[] = { - "Track1Bitrate", "Track2Bitrate", "Track3Bitrate", "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", - }; - int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]); - return FindClosestAvailableAudioBitrate(id, bitrate); -} - -inline std::optional AdvancedOutput::VodTrackMixerIdx(obs_service_t *service) -{ - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - bool vodTrackEnabled = config_get_bool(main->Config(), "AdvOut", "VodTrackEnabled"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) { - vodTrackEnabled = enableForCustomServer ? vodTrackEnabled : false; - } else { - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *service = obs_data_get_string(settings, "service"); - if (!ServiceSupportsVodTrack(service)) - vodTrackEnabled = false; - } - - if (vodTrackEnabled && streamTrackIndex != vodTrackIndex) - return {vodTrackIndex - 1}; - return std::nullopt; -} - -inline void AdvancedOutput::SetupVodTrack(obs_service_t *service) -{ - if (VodTrackMixerIdx(service).has_value()) - obs_output_set_audio_encoder(streamOutput, streamArchiveEnc, 1); - else - clear_archive_encoder(streamOutput, ADV_ARCHIVE_NAME); -} - -std::shared_future AdvancedOutput::SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) -{ - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - - bool is_multitrack_output = allowsMultiTrack(); - - if (!useStreamEncoder || (!ffmpegOutput && !obs_output_active(fileOutput))) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - const char *audio_encoder_id = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "adv_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - int idx = 0; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - return true; - }; - - return SetupMultitrackVideo(service, audio_encoder_id, static_cast(streamTrackIndex), - VodTrackMixerIdx(service), [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -bool AdvancedOutput::StartStreaming(obs_service_t *service) -{ - obs_output_set_service(streamOutput, service); - - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_int(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_int(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - bool is_rtmp = false; - obs_service_t *service_obj = main->GetService(); - const char *protocol = obs_service_get_protocol(service_obj); - if (protocol) { - if (astrcmpi_n(protocol, RTMP_PROTOCOL, strlen(RTMP_PROTOCOL)) == 0) - is_rtmp = true; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - if (is_rtmp) { - SetupVodTrack(service); - } - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -bool AdvancedOutput::StartRecording() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - bool splitFile; - const char *splitFileType; - int splitFileTime; - int splitFileSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) { - UpdateRecordingSettings(); - } - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - splitFile = config_get_bool(main->Config(), "AdvOut", "RecSplitFile"); - - string strPath = GetRecordingFilename(path, recFormat, noSpace, overwriteIfExists, filenameFormat, - ffmpegRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, ffmpegRecording ? "url" : "path", strPath.c_str()); - - if (splitFile) { - splitFileType = config_get_string(main->Config(), "AdvOut", "RecSplitFileType"); - splitFileTime = (astrcmpi(splitFileType, "Time") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileTime") - : 0; - splitFileSize = (astrcmpi(splitFileType, "Size") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileSize") - : 0; - string ext = GetFormatExt(recFormat); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", filenameFormat); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_bool(settings, "allow_overwrite", overwriteIfExists); - obs_data_set_bool(settings, "split_file", true); - obs_data_set_int(settings, "max_time_sec", splitFileTime * 60); - obs_data_set_int(settings, "max_size_mb", splitFileSize); - } - - obs_output_update(fileOutput, settings); - } - - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool AdvancedOutput::StartReplayBuffer() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - const char *rbPrefix; - const char *rbSuffix; - int rbTime; - int rbSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - rbTime = config_get_int(main->Config(), "AdvOut", "RecRBTime"); - rbSize = config_get_int(main->Config(), "AdvOut", "RecRBSize"); - - string f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(recFormat); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usesBitrate ? 0 : rbSize); - - obs_output_update(replayBuffer, settings); - } - - if (!obs_output_start(replayBuffer)) { - QString error_reason; - const char *error = obs_output_get_last_error(replayBuffer); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), error_reason); - return false; - } - - return true; -} - -void AdvancedOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void AdvancedOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void AdvancedOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool AdvancedOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool AdvancedOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool AdvancedOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - -void BasicOutputHandler::SetupAutoRemux(const char *&container) -{ - bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux"); - if (autoRemux && strcmp(container, "mp4") == 0) - container = "mkv"; -} - -std::string BasicOutputHandler::GetRecordingFilename(const char *path, const char *container, bool noSpace, - bool overwrite, const char *format, bool ffmpeg) -{ - if (!ffmpeg) - SetupAutoRemux(container); - - string dst = GetOutputFilename(path, container, noSpace, overwrite, format); - lastRecordingPath = dst; - return dst; -} - -extern std::string DeserializeConfigText(const char *text); - -std::shared_future BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service, std::string audio_encoder_id, - size_t main_audio_mixer, - std::optional vod_track_mixer, - std::function)> continuation) -{ - auto start_streaming_guard = std::make_shared(); - if (!multitrackVideo) { - continuation(std::nullopt); - return start_streaming_guard->GetFuture(); - } - - multitrackVideoActive = false; - - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - bool is_custom = strncmp("rtmp_custom", obs_service_get_type(service), 11) == 0; - - std::optional custom_config = std::nullopt; - if (config_get_bool(main->Config(), "Stream1", "MultitrackVideoConfigOverrideEnabled")) - custom_config = DeserializeConfigText( - config_get_string(main->Config(), "Stream1", "MultitrackVideoConfigOverride")); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - QString key = obs_data_get_string(settings, "key"); - - const char *service_name = ""; - if (is_custom && obs_data_has_user_value(settings, "service_name")) { - service_name = obs_data_get_string(settings, "service_name"); - } else if (!is_custom) { - service_name = obs_data_get_string(settings, "service"); - } - - std::optional custom_rtmp_url; - std::optional use_rtmps; - auto server = obs_data_get_string(settings, "server"); - if (strncmp(server, "auto", 4) != 0) { - custom_rtmp_url = server; - } else { - QString server_ = server; - use_rtmps = server_.contains("rtmps", Qt::CaseInsensitive); - } - - auto service_custom_server = obs_data_get_bool(settings, "using_custom_server"); - if (custom_rtmp_url.has_value()) { - blog(LOG_INFO, "Using %sserver '%s'", service_custom_server ? "custom " : "", custom_rtmp_url->c_str()); - } - - auto maximum_aggregate_bitrate = - config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto") - ? std::nullopt - : std::make_optional( - config_get_int(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrate")); - - auto maximum_video_tracks = config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracksAuto") - ? std::nullopt - : std::make_optional(config_get_int( - main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracks")); - - auto stream_dump_config = GenerateMultitrackVideoStreamDumpConfig(); - - auto continue_on_main_thread = [&, start_streaming_guard, service = OBSService{service}, - continuation = - std::move(continuation)](std::optional error) { - if (error) { - OBSDataAutoRelease service_settings = obs_service_get_settings(service); - auto multitrack_video_name = QTStr("Basic.Settings.Stream.MultitrackVideoLabel"); - if (obs_data_has_user_value(service_settings, "multitrack_video_name")) { - multitrack_video_name = obs_data_get_string(service_settings, "multitrack_video_name"); - } - - multitrackVideoActive = false; - if (!error->ShowDialog(main, multitrack_video_name)) - return continuation(false); - return continuation(std::nullopt); - } - - multitrackVideoActive = true; - - auto signal_handler = multitrackVideo->StreamingSignalHandler(); - - streamDelayStarting.Connect(signal_handler, "starting", OBSStreamStarting, this); - streamStopping.Connect(signal_handler, "stopping", OBSStreamStopping, this); - - startStreaming.Connect(signal_handler, "start", OBSStartStreaming, this); - stopStreaming.Connect(signal_handler, "stop", OBSStopStreaming, this); - return continuation(true); - }; - - QThreadPool::globalInstance()->start([=, multitrackVideo = multitrackVideo.get(), - service_name = std::string{service_name}, service = OBSService{service}, - stream_dump_config = OBSData{stream_dump_config}, - start_streaming_guard = start_streaming_guard]() mutable { - std::optional error; - try { - multitrackVideo->PrepareStreaming(main, service_name.c_str(), service, custom_rtmp_url, key, - audio_encoder_id.c_str(), maximum_aggregate_bitrate, - maximum_video_tracks, custom_config, stream_dump_config, - main_audio_mixer, vod_track_mixer, use_rtmps); - } catch (const MultitrackVideoError &error_) { - error.emplace(error_); - } - - QMetaObject::invokeMethod(main, [=] { continue_on_main_thread(error); }); - }); - - return start_streaming_guard->GetFuture(); -} - -OBSDataAutoRelease BasicOutputHandler::GenerateMultitrackVideoStreamDumpConfig() -{ - auto stream_dump_enabled = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpEnabled"); - - if (!stream_dump_enabled) - return nullptr; - - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - bool useMP4 = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpAsMP4"); - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, useMP4 ? "mp4" : "flv", noSpace, overwriteIfExists, f.c_str(), - // never remux stream dump - false); - obs_data_set_string(settings, "path", strPath.c_str()); - - if (useMP4) { - obs_data_set_bool(settings, "use_mp4", true); - obs_data_set_string(settings, "muxer_settings", "write_encoder_info=1"); - } - - return settings; -} - -BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main) -{ - return new SimpleOutput(main); -} - -BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main) -{ - return new AdvancedOutput(main); -} diff --git a/frontend/utility/StartMultiTrackVideoStreamingGuard.hpp b/frontend/utility/StartMultiTrackVideoStreamingGuard.hpp index 30d203174..f0535f4d2 100644 --- a/frontend/utility/StartMultiTrackVideoStreamingGuard.hpp +++ b/frontend/utility/StartMultiTrackVideoStreamingGuard.hpp @@ -1,175 +1,6 @@ -#include -#include -#include -#include -#include -#include -#include "audio-encoders.hpp" -#include "multitrack-video-error.hpp" -#include "window-basic-main.hpp" -#include "window-basic-main-outputs.hpp" -#include "window-basic-vcam.hpp" +#pragma once -using namespace std; - -extern bool EncoderAvailable(const char *encoder); - -volatile bool streaming_active = false; -volatile bool recording_active = false; -volatile bool recording_paused = false; -volatile bool replaybuf_active = false; -volatile bool virtualcam_active = false; - -#define RTMP_PROTOCOL "rtmp" -#define SRT_PROTOCOL "srt" -#define RIST_PROTOCOL "rist" - -static void OBSStreamStarting(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - return; - - output->delayActive = true; - QMetaObject::invokeMethod(output->main, "StreamDelayStarting", Q_ARG(int, sec)); -} - -static void OBSStreamStopping(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - obs_output_t *obj = (obs_output_t *)calldata_ptr(params, "output"); - - int sec = (int)obs_output_get_active_delay(obj); - if (sec == 0) - QMetaObject::invokeMethod(output->main, "StreamStopping"); - else - QMetaObject::invokeMethod(output->main, "StreamDelayStopping", Q_ARG(int, sec)); -} - -static void OBSStartStreaming(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->streamingActive = true; - os_atomic_set_bool(&streaming_active, true); - QMetaObject::invokeMethod(output->main, "StreamingStart"); -} - -static void OBSStopStreaming(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->streamingActive = false; - output->delayActive = false; - output->multitrackVideoActive = false; - os_atomic_set_bool(&streaming_active, false); - QMetaObject::invokeMethod(output->main, "StreamingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSStartRecording(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->recordingActive = true; - os_atomic_set_bool(&recording_active, true); - QMetaObject::invokeMethod(output->main, "RecordingStart"); -} - -static void OBSStopRecording(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - const char *last_error = calldata_string(params, "last_error"); - - QString arg_last_error = QString::fromUtf8(last_error); - - output->recordingActive = false; - os_atomic_set_bool(&recording_active, false); - os_atomic_set_bool(&recording_paused, false); - QMetaObject::invokeMethod(output->main, "RecordingStop", Q_ARG(int, code), Q_ARG(QString, arg_last_error)); -} - -static void OBSRecordStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "RecordStopping"); -} - -static void OBSRecordFileChanged(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - const char *next_file = calldata_string(params, "next_file"); - - QString arg_last_file = QString::fromUtf8(output->lastRecordingPath.c_str()); - - QMetaObject::invokeMethod(output->main, "RecordingFileChanged", Q_ARG(QString, arg_last_file)); - - output->lastRecordingPath = next_file; -} - -static void OBSStartReplayBuffer(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->replayBufferActive = true; - os_atomic_set_bool(&replaybuf_active, true); - QMetaObject::invokeMethod(output->main, "ReplayBufferStart"); -} - -static void OBSStopReplayBuffer(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->replayBufferActive = false; - os_atomic_set_bool(&replaybuf_active, false); - QMetaObject::invokeMethod(output->main, "ReplayBufferStop", Q_ARG(int, code)); -} - -static void OBSReplayBufferStopping(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferStopping"); -} - -static void OBSReplayBufferSaved(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - QMetaObject::invokeMethod(output->main, "ReplayBufferSaved", Qt::QueuedConnection); -} - -static void OBSStartVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - - output->virtualCamActive = true; - os_atomic_set_bool(&virtualcam_active, true); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStart"); -} - -static void OBSStopVirtualCam(void *data, calldata_t *params) -{ - BasicOutputHandler *output = static_cast(data); - int code = (int)calldata_int(params, "code"); - - output->virtualCamActive = false; - os_atomic_set_bool(&virtualcam_active, false); - QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", Q_ARG(int, code)); -} - -static void OBSDeactivateVirtualCam(void *data, calldata_t * /* params */) -{ - BasicOutputHandler *output = static_cast(data); - output->DestroyVirtualCamView(); -} - -/* ------------------------------------------------------------------------ */ +#include struct StartMultitrackVideoStreamingGuard { StartMultitrackVideoStreamingGuard() { future = guard.get_future().share(); }; @@ -187,2355 +18,3 @@ private: std::promise guard; std::shared_future future; }; - -/* ------------------------------------------------------------------------ */ - -static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleAACEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static bool CreateSimpleOpusEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx) -{ - const char *id_ = GetSimpleOpusEncoderForBitrate(bitrate); - if (!id_) { - res = nullptr; - return false; - } - - res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); - - if (res) { - obs_encoder_release(res); - return true; - } - - return false; -} - -static inline bool can_use_output(const char *prot, const char *output, const char *prot_test1, - const char *prot_test2 = nullptr) -{ - return (strcmp(prot, prot_test1) == 0 || (prot_test2 && strcmp(prot, prot_test2) == 0)) && - (obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0; -} - -static bool return_first_id(void *data, const char *id) -{ - const char **output = (const char **)data; - - *output = id; - return false; -} - -static const char *GetStreamOutputType(const obs_service_t *service) -{ - const char *protocol = obs_service_get_protocol(service); - const char *output = nullptr; - - if (!protocol) { - blog(LOG_WARNING, "The service '%s' has no protocol set", obs_service_get_id(service)); - return nullptr; - } - - if (!obs_is_output_protocol_registered(protocol)) { - blog(LOG_WARNING, "The protocol '%s' is not registered", protocol); - return nullptr; - } - - /* Check if the service has a preferred output type */ - output = obs_service_get_preferred_output_type(service); - if (output) { - if ((obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0) - return output; - - blog(LOG_WARNING, "The output '%s' is not registered, fallback to another one", output); - } - - /* Otherwise, prefer first-party output types */ - if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) { - return "rtmp_output"; - } else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) { - return "ffmpeg_hls_muxer"; - } else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT", "RIST")) { - return "ffmpeg_mpegts_muxer"; - } - - /* If third-party protocol, use the first enumerated type */ - obs_enum_output_types_with_protocol(protocol, &output, return_first_id); - if (output) - return output; - - blog(LOG_WARNING, "No output compatible with the service '%s' is registered", obs_service_get_id(service)); - - return nullptr; -} - -/* ------------------------------------------------------------------------ */ - -inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) -{ - if (main->vcamEnabled) { - virtualCam = obs_output_create(VIRTUAL_CAM_ID, "virtualcam_output", nullptr, nullptr); - - signal_handler_t *signal = obs_output_get_signal_handler(virtualCam); - startVirtualCam.Connect(signal, "start", OBSStartVirtualCam, this); - stopVirtualCam.Connect(signal, "stop", OBSStopVirtualCam, this); - deactivateVirtualCam.Connect(signal, "deactivate", OBSDeactivateVirtualCam, this); - } - - auto multitrack_enabled = config_get_bool(main->Config(), "Stream1", "EnableMultitrackVideo"); - if (!config_has_user_value(main->Config(), "Stream1", "EnableMultitrackVideo")) { - auto service = main_->GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - multitrack_enabled = obs_data_has_user_value(settings, "multitrack_video_configuration_url"); - } - if (multitrack_enabled) - multitrackVideo = make_unique(); -} - -extern void log_vcam_changed(const VCamConfig &config, bool starting); - -bool BasicOutputHandler::StartVirtualCam() -{ - if (!main->vcamEnabled) - return false; - - bool typeIsProgram = main->vcamConfig.type == VCamOutputType::ProgramView; - - if (!virtualCamView && !typeIsProgram) - virtualCamView = obs_view_create(); - - UpdateVirtualCamOutputSource(); - - if (!virtualCamVideo) { - virtualCamVideo = typeIsProgram ? obs_get_video() : obs_view_add(virtualCamView); - - if (!virtualCamVideo) - return false; - } - - obs_output_set_media(virtualCam, virtualCamVideo, obs_get_audio()); - if (!Active()) - SetupOutputs(); - - bool success = obs_output_start(virtualCam); - if (!success) { - QString errorReason; - - const char *error = obs_output_get_last_error(virtualCam); - if (error) { - errorReason = QT_UTF8(error); - } else { - errorReason = QTStr("Output.StartFailedGeneric"); - } - - QMessageBox::critical(main, QTStr("Output.StartVirtualCamFailed"), errorReason); - - DestroyVirtualCamView(); - } - - log_vcam_changed(main->vcamConfig, true); - - return success; -} - -void BasicOutputHandler::StopVirtualCam() -{ - if (main->vcamEnabled) { - obs_output_stop(virtualCam); - } -} - -bool BasicOutputHandler::VirtualCamActive() const -{ - if (main->vcamEnabled) { - return obs_output_active(virtualCam); - } - return false; -} - -void BasicOutputHandler::UpdateVirtualCamOutputSource() -{ - if (!main->vcamEnabled || !virtualCamView) - return; - - OBSSourceAutoRelease source; - - switch (main->vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - DestroyVirtualCameraScene(); - return; - case VCamOutputType::PreviewOutput: { - DestroyVirtualCameraScene(); - OBSSource s = main->GetCurrentSceneSource(); - obs_source_get_ref(s); - source = s.Get(); - break; - } - case VCamOutputType::SceneOutput: - DestroyVirtualCameraScene(); - source = obs_get_source_by_name(main->vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - OBSSourceAutoRelease s = obs_get_source_by_name(main->vcamConfig.source.c_str()); - - if (!vCamSourceScene) - vCamSourceScene = obs_scene_create_private("vcam_source"); - source = obs_source_get_ref(obs_scene_get_source(vCamSourceScene)); - - if (vCamSourceSceneItem && (obs_sceneitem_get_source(vCamSourceSceneItem) != s)) { - obs_sceneitem_remove(vCamSourceSceneItem); - vCamSourceSceneItem = nullptr; - } - - if (!vCamSourceSceneItem) { - vCamSourceSceneItem = obs_scene_add(vCamSourceScene, s); - - obs_sceneitem_set_bounds_type(vCamSourceSceneItem, OBS_BOUNDS_SCALE_INNER); - obs_sceneitem_set_bounds_alignment(vCamSourceSceneItem, OBS_ALIGN_CENTER); - - const struct vec2 size = { - (float)obs_source_get_width(source), - (float)obs_source_get_height(source), - }; - obs_sceneitem_set_bounds(vCamSourceSceneItem, &size); - } - break; - } - - OBSSourceAutoRelease current = obs_view_get_source(virtualCamView, 0); - if (source != current) - obs_view_set_source(virtualCamView, 0, source); -} - -void BasicOutputHandler::DestroyVirtualCamView() -{ - if (main->vcamConfig.type == VCamOutputType::ProgramView) { - virtualCamVideo = nullptr; - return; - } - - obs_view_remove(virtualCamView); - obs_view_set_source(virtualCamView, 0, nullptr); - virtualCamVideo = nullptr; - - obs_view_destroy(virtualCamView); - virtualCamView = nullptr; - - DestroyVirtualCameraScene(); -} - -void BasicOutputHandler::DestroyVirtualCameraScene() -{ - if (!vCamSourceScene) - return; - - obs_scene_release(vCamSourceScene); - vCamSourceScene = nullptr; - vCamSourceSceneItem = nullptr; -} - -/* ------------------------------------------------------------------------ */ - -struct SimpleOutput : BasicOutputHandler { - OBSEncoder audioStreaming; - OBSEncoder videoStreaming; - OBSEncoder audioRecording; - OBSEncoder audioArchive; - OBSEncoder videoRecording; - OBSEncoder audioTrack[MAX_AUDIO_MIXES]; - - string videoEncoder; - string videoQuality; - bool usingRecordingPreset = false; - bool recordingConfigured = false; - bool ffmpegOutput = false; - bool lowCPUx264 = false; - - SimpleOutput(OBSBasic *main_); - - int CalcCRF(int crf); - - void UpdateRecordingSettings_x264_crf(int crf); - void UpdateRecordingSettings_qsv11(int crf, bool av1); - void UpdateRecordingSettings_nvenc(int cqp); - void UpdateRecordingSettings_nvenc_hevc_av1(int cqp); - void UpdateRecordingSettings_amd_cqp(int cqp); - void UpdateRecordingSettings_apple(int quality); -#ifdef ENABLE_HEVC - void UpdateRecordingSettings_apple_hevc(int quality); -#endif - void UpdateRecordingSettings(); - void UpdateRecordingAudioSettings(); - virtual void Update() override; - - void SetupOutputs() override; - int GetAudioBitrate() const; - - void LoadRecordingPreset_Lossy(const char *encoder); - void LoadRecordingPreset_Lossless(); - void LoadRecordingPreset(); - - void LoadStreamingPreset_Lossy(const char *encoder); - - void UpdateRecording(); - bool ConfigureRecording(bool useReplayBuffer); - - bool IsVodTrackEnabled(obs_service_t *service); - void SetupVodTrack(obs_service_t *service); - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; -}; - -void SimpleOutput::LoadRecordingPreset_Lossless() -{ - fileOutput = obs_output_create("ffmpeg_output", "simple_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(simple output)"; - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "format_name", "avi"); - obs_data_set_string(settings, "video_encoder", "utvideo"); - obs_data_set_string(settings, "audio_encoder", "pcm_s16le"); - - obs_output_update(fileOutput, settings); -} - -void SimpleOutput::LoadRecordingPreset_Lossy(const char *encoderId) -{ - videoRecording = obs_video_encoder_create(encoderId, "simple_video_recording", nullptr, nullptr); - if (!videoRecording) - throw "Failed to create video recording encoder (simple output)"; - obs_encoder_release(videoRecording); -} - -void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId) -{ - videoStreaming = obs_video_encoder_create(encoderId, "simple_video_stream", nullptr, nullptr); - if (!videoStreaming) - throw "Failed to create video streaming encoder (simple output)"; - obs_encoder_release(videoStreaming); -} - -/* mistakes have been made to lead us to this. */ -const char *get_simple_output_encoder(const char *encoder) -{ - if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) { - return "obs_x264"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - return "obs_qsv11_v2"; - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - return "obs_qsv11_av1"; - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - return "h264_texture_amf"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - return "h265_texture_amf"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - return "av1_texture_amf"; - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - return EncoderAvailable("obs_nvenc_h264_tex") ? "obs_nvenc_h264_tex" : "ffmpeg_nvenc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - return EncoderAvailable("obs_nvenc_hevc_tex") ? "obs_nvenc_hevc_tex" : "ffmpeg_hevc_nvenc"; -#endif - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - return "obs_nvenc_av1_tex"; - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.avc"; -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) { - return "com.apple.videotoolbox.videoencoder.ave.hevc"; -#endif - } - - return "obs_x264"; -} - -void SimpleOutput::LoadRecordingPreset() -{ - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "RecEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "RecAudioEncoder"); - - videoEncoder = encoder; - videoQuality = quality; - ffmpegOutput = false; - - if (strcmp(quality, "Stream") == 0) { - videoRecording = videoStreaming; - audioRecording = audioStreaming; - usingRecordingPreset = false; - return; - - } else if (strcmp(quality, "Lossless") == 0) { - LoadRecordingPreset_Lossless(); - usingRecordingPreset = true; - ffmpegOutput = true; - return; - - } else { - lowCPUx264 = false; - - if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) - lowCPUx264 = true; - LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder)); - usingRecordingPreset = true; - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioRecording, 192, "simple_opus_recording", 0); - else - success = CreateSimpleAACEncoder(audioRecording, 192, "simple_aac_recording", 0); - - if (!success) - throw "Failed to create audio recording encoder " - "(simple output)"; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[23]; - if (strcmp(audio_encoder, "opus") == 0) { - snprintf(name, sizeof name, "simple_opus_recording%d", i); - success = CreateSimpleOpusEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } else { - snprintf(name, sizeof name, "simple_aac_recording%d", i); - success = CreateSimpleAACEncoder(audioTrack[i], GetAudioBitrate(), name, i); - } - if (!success) - throw "Failed to create multi-track audio recording encoder " - "(simple output)"; - } - } -} - -#define SIMPLE_ARCHIVE_NAME "simple_archive_audio" - -SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - - LoadStreamingPreset_Lossy(get_simple_output_encoder(encoder)); - - bool success = false; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioStreaming, GetAudioBitrate(), "simple_opus", 0); - else - success = CreateSimpleAACEncoder(audioStreaming, GetAudioBitrate(), "simple_aac", 0); - - if (!success) - throw "Failed to create audio streaming encoder (simple output)"; - - if (strcmp(audio_encoder, "opus") == 0) - success = CreateSimpleOpusEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - else - success = CreateSimpleAACEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1); - - if (!success) - throw "Failed to create audio archive encoder (simple output)"; - - LoadRecordingPreset(); - - if (!ffmpegOutput) { - bool useReplayBuffer = config_get_bool(main->Config(), "SimpleOutput", "RecRB"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool use_native = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(use_native ? "mp4_output" : "ffmpeg_muxer", "simple_file_output", - nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(simple output)"; - } - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); -} - -int SimpleOutput::GetAudioBitrate() const -{ - const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder"); - int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); - - if (strcmp(audio_encoder, "opus") == 0) - return FindClosestAvailableSimpleOpusBitrate(bitrate); - - return FindClosestAvailableSimpleAACBitrate(bitrate); -} - -void SimpleOutput::Update() -{ - OBSDataAutoRelease videoSettings = obs_data_create(); - OBSDataAutoRelease audioSettings = obs_data_create(); - - int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); - int audioBitrate = GetAudioBitrate(); - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - const char *custom = config_get_string(main->Config(), "SimpleOutput", "x264Settings"); - const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); - const char *encoder_id = obs_encoder_get_id(videoStreaming); - const char *presetType; - const char *preset; - - if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) { - presetType = "QSVPreset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) { - presetType = "AMDPreset"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) { - presetType = "AMDPreset"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) { - presetType = "NVENCPreset2"; - -#ifdef ENABLE_HEVC - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) { - presetType = "NVENCPreset2"; -#endif - - } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) { - presetType = "AMDAV1Preset"; - - } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) { - presetType = "NVENCPreset2"; - - } else { - presetType = "Preset"; - } - - preset = config_get_string(main->Config(), "SimpleOutput", presetType); - - /* Only use preset2 for legacy/FFmpeg NVENC Encoder. */ - if (strncmp(encoder_id, "ffmpeg_", 7) == 0 && strcmp(presetType, "NVENCPreset2") == 0) { - obs_data_set_string(videoSettings, "preset2", preset); - } else { - obs_data_set_string(videoSettings, "preset", preset); - } - - obs_data_set_string(videoSettings, "rate_control", "CBR"); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - - if (advanced) - obs_data_set_string(videoSettings, "x264opts", custom); - - obs_data_set_string(audioSettings, "rate_control", "CBR"); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - - obs_service_apply_encoder_settings(main->GetService(), videoSettings, audioSettings); - - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(videoSettings, "bitrate", videoBitrate); - obs_data_set_int(audioSettings, "bitrate", audioBitrate); - } - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, videoSettings); - obs_encoder_update(audioStreaming, audioSettings); - obs_encoder_update(audioArchive, audioSettings); -} - -void SimpleOutput::UpdateRecordingAudioSettings() -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", 192); - obs_data_set_string(settings, "rate_control", "CBR"); - - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv || strcmp(quality, "Stream") == 0) { - obs_encoder_update(audioRecording, settings); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_update(audioTrack[i], settings); - } - } - } -} - -#define CROSS_DIST_CUTOFF 2000.0 - -int SimpleOutput::CalcCRF(int crf) -{ - int cx = config_get_uint(main->Config(), "Video", "OutputCX"); - int cy = config_get_uint(main->Config(), "Video", "OutputCY"); - double fCX = double(cx); - double fCY = double(cy); - - if (lowCPUx264) - crf -= 2; - - double crossDist = sqrt(fCX * fCX + fCY * fCY); - double crfResReduction = fmin(CROSS_DIST_CUTOFF, crossDist) / CROSS_DIST_CUTOFF; - crfResReduction = (1.0 - crfResReduction) * 10.0; - - return crf - int(crfResReduction); -} - -void SimpleOutput::UpdateRecordingSettings_x264_crf(int crf) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "crf", crf); - obs_data_set_bool(settings, "use_bufsize", true); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", lowCPUx264 ? "ultrafast" : "veryfast"); - - obs_encoder_update(videoRecording, settings); -} - -static bool icq_available(obs_encoder_t *encoder) -{ - obs_properties_t *props = obs_encoder_properties(encoder); - obs_property_t *p = obs_properties_get(props, "rate_control"); - bool icq_found = false; - - size_t num = obs_property_list_item_count(p); - for (size_t i = 0; i < num; i++) { - const char *val = obs_property_list_item_string(p, i); - if (strcmp(val, "ICQ") == 0) { - icq_found = true; - break; - } - } - - obs_properties_destroy(props); - return icq_found; -} - -void SimpleOutput::UpdateRecordingSettings_qsv11(int crf, bool av1) -{ - bool icq = icq_available(videoRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "profile", "high"); - - if (icq && !av1) { - obs_data_set_string(settings, "rate_control", "ICQ"); - obs_data_set_int(settings, "icq_quality", crf); - } else { - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_int(settings, "cqp", crf); - } - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_nvenc_hevc_av1(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "cqp", cqp); - - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings_apple(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} - -#ifdef ENABLE_HEVC -void SimpleOutput::UpdateRecordingSettings_apple_hevc(int quality) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CRF"); - obs_data_set_string(settings, "profile", "main"); - obs_data_set_int(settings, "quality", quality); - - obs_encoder_update(videoRecording, settings); -} -#endif - -void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp) -{ - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "rate_control", "CQP"); - obs_data_set_string(settings, "profile", "high"); - obs_data_set_string(settings, "preset", "quality"); - obs_data_set_int(settings, "cqp", cqp); - obs_encoder_update(videoRecording, settings); -} - -void SimpleOutput::UpdateRecordingSettings() -{ - bool ultra_hq = (videoQuality == "HQ"); - int crf = CalcCRF(ultra_hq ? 16 : 23); - - if (astrcmp_n(videoEncoder.c_str(), "x264", 4) == 0) { - UpdateRecordingSettings_x264_crf(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV) { - UpdateRecordingSettings_qsv11(crf, false); - - } else if (videoEncoder == SIMPLE_ENCODER_QSV_AV1) { - UpdateRecordingSettings_qsv11(crf, true); - - } else if (videoEncoder == SIMPLE_ENCODER_AMD) { - UpdateRecordingSettings_amd_cqp(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_AMD_HEVC) { - UpdateRecordingSettings_amd_cqp(crf); -#endif - - } else if (videoEncoder == SIMPLE_ENCODER_AMD_AV1) { - UpdateRecordingSettings_amd_cqp(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_NVENC) { - UpdateRecordingSettings_nvenc(crf); - -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_HEVC) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); -#endif - } else if (videoEncoder == SIMPLE_ENCODER_NVENC_AV1) { - UpdateRecordingSettings_nvenc_hevc_av1(crf); - - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_H264) { - /* These are magic numbers. 0 - 100, more is better. */ - UpdateRecordingSettings_apple(ultra_hq ? 70 : 50); -#ifdef ENABLE_HEVC - } else if (videoEncoder == SIMPLE_ENCODER_APPLE_HEVC) { - UpdateRecordingSettings_apple_hevc(ultra_hq ? 70 : 50); -#endif - } - UpdateRecordingAudioSettings(); -} - -inline void SimpleOutput::SetupOutputs() -{ - SimpleOutput::Update(); - obs_encoder_set_video(videoStreaming, obs_get_video()); - obs_encoder_set_audio(audioStreaming, obs_get_audio()); - obs_encoder_set_audio(audioArchive, obs_get_audio()); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - - if (usingRecordingPreset) { - if (ffmpegOutput) { - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - } else { - obs_encoder_set_video(videoRecording, obs_get_video()); - if (flv) { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_encoder_set_audio(audioTrack[i], obs_get_audio()); - } - } - } - } - } else { - obs_encoder_set_audio(audioRecording, obs_get_audio()); - } -} - -const char *FindAudioEncoderFromCodec(const char *type) -{ - const char *alt_enc_id = nullptr; - size_t i = 0; - - while (obs_enum_encoder_types(i++, &alt_enc_id)) { - const char *codec = obs_get_encoder_codec(alt_enc_id); - if (strcmp(type, codec) == 0) { - return alt_enc_id; - } - } - - return nullptr; -} - -std::shared_future SimpleOutput::SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation) -{ - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - auto audio_bitrate = GetAudioBitrate(); - auto vod_track_mixer = IsVodTrackEnabled(service) ? std::optional{1} : std::nullopt; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "simple_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, audioStreaming, 0); - obs_output_set_service(streamOutput, service); - return true; - }; - - return SetupMultitrackVideo(service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0, vod_track_mixer, - [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -static inline bool ServiceSupportsVodTrack(const char *service); - -static void clear_archive_encoder(obs_output_t *output, const char *expected_name) -{ - obs_encoder_t *last = obs_output_get_audio_encoder(output, 1); - bool clear = false; - - /* ensures that we don't remove twitch's soundtrack encoder */ - if (last) { - const char *name = obs_encoder_get_name(last); - clear = name && strcmp(name, expected_name) == 0; - obs_encoder_release(last); - } - - if (clear) - obs_output_set_audio_encoder(output, nullptr, 1); -} - -bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service) -{ - bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced"); - bool enable = config_get_bool(main->Config(), "SimpleOutput", "VodTrackEnabled"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *name = obs_data_get_string(settings, "service"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) - return enableForCustomServer ? enable : false; - else - return advanced && enable && ServiceSupportsVodTrack(name); -} - -void SimpleOutput::SetupVodTrack(obs_service_t *service) -{ - if (IsVodTrackEnabled(service)) - obs_output_set_audio_encoder(streamOutput, audioArchive, 1); - else - clear_archive_encoder(streamOutput, SIMPLE_ARCHIVE_NAME); -} - -bool SimpleOutput::StartStreaming(obs_service_t *service) -{ - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_uint(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_uint(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - - if (!multitrackVideo || !multitrackVideoActive) - SetupVodTrack(service); - - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -void SimpleOutput::UpdateRecording() -{ - const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - bool flv = strcmp(recFormat, "flv") == 0; - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - int idx = 0; - int idx2 = 0; - const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality"); - - if (replayBufferActive || recordingActive) - return; - - if (usingRecordingPreset) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(streamOutput)) { - Update(); - } - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput) { - obs_output_set_video_encoder(fileOutput, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(fileOutput, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, audioTrack[i], idx++); - } - } - } - } - if (replayBuffer) { - obs_output_set_video_encoder(replayBuffer, videoRecording); - if (flv || strcmp(quality, "Stream") == 0) { - obs_output_set_audio_encoder(replayBuffer, audioRecording, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(replayBuffer, audioTrack[i], idx2++); - } - } - } - } - - recordingConfigured = true; -} - -bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer) -{ - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - const char *format = config_get_string(main->Config(), "SimpleOutput", "RecFormat2"); - const char *mux = config_get_string(main->Config(), "SimpleOutput", "MuxerCustom"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - int rbTime = config_get_int(main->Config(), "SimpleOutput", "RecRBTime"); - int rbSize = config_get_int(main->Config(), "SimpleOutput", "RecRBSize"); - int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks"); - - bool is_fragmented = strncmp(format, "fragmented", 10) == 0; - bool is_lossless = videoQuality == "Lossless"; - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - if (updateReplayBuffer) { - f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(format); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usingRecordingPreset ? rbSize : 0); - } else { - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, ffmpegOutput ? "avi" : format, noSpace, overwriteIfExists, - f.c_str(), ffmpegOutput); - obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str()); - if (ffmpegOutput) - obs_output_set_mixers(fileOutput, tracks); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && !is_lossless && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented && !is_lossless) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - if (updateReplayBuffer) - obs_output_update(replayBuffer, settings); - else - obs_output_update(fileOutput, settings); - - return true; -} - -bool SimpleOutput::StartRecording() -{ - UpdateRecording(); - if (!ConfigureRecording(false)) - return false; - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool SimpleOutput::StartReplayBuffer() -{ - UpdateRecording(); - if (!ConfigureRecording(true)) - return false; - if (!obs_output_start(replayBuffer)) { - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), QTStr("Output.StartFailedGeneric")); - return false; - } - - return true; -} - -void SimpleOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void SimpleOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void SimpleOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool SimpleOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool SimpleOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool SimpleOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - -struct AdvancedOutput : BasicOutputHandler { - OBSEncoder streamAudioEnc; - OBSEncoder streamArchiveEnc; - OBSEncoder streamTrack[MAX_AUDIO_MIXES]; - OBSEncoder recordTrack[MAX_AUDIO_MIXES]; - OBSEncoder videoStreaming; - OBSEncoder videoRecording; - - bool ffmpegOutput; - bool ffmpegRecording; - bool useStreamEncoder; - bool useStreamAudioEncoder; - bool usesBitrate = false; - - AdvancedOutput(OBSBasic *main_); - - inline void UpdateStreamSettings(); - inline void UpdateRecordingSettings(); - inline void UpdateAudioSettings(); - virtual void Update() override; - - inline std::optional VodTrackMixerIdx(obs_service_t *service); - inline void SetupVodTrack(obs_service_t *service); - - inline void SetupStreaming(); - inline void SetupRecording(); - inline void SetupFFmpeg(); - void SetupOutputs() override; - int GetAudioBitrate(size_t i, const char *id) const; - - virtual std::shared_future SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) override; - virtual bool StartStreaming(obs_service_t *service) override; - virtual bool StartRecording() override; - virtual bool StartReplayBuffer() override; - virtual void StopStreaming(bool force) override; - virtual void StopRecording(bool force) override; - virtual void StopReplayBuffer(bool force) override; - virtual bool StreamingActive() const override; - virtual bool RecordingActive() const override; - virtual bool ReplayBufferActive() const override; - bool allowsMultiTrack(); -}; - -static OBSData GetDataFromJsonFile(const char *jsonFile) -{ - const OBSBasic *basic = reinterpret_cast(App()->GetMainWindow()); - - const OBSProfile ¤tProfile = basic->GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(jsonFile); - - OBSDataAutoRelease data = nullptr; - - if (!jsonFilePath.empty()) { - BPtr jsonData = os_quick_read_utf8_file(jsonFilePath.u8string().c_str()); - - if (!!jsonData) { - data = obs_data_create_from_json(jsonData); - } - } - - if (!data) { - data = obs_data_create(); - } - - return data.Get(); -} - -static void ApplyEncoderDefaults(OBSData &settings, const obs_encoder_t *encoder) -{ - OBSData dataRet = obs_encoder_get_defaults(encoder); - obs_data_release(dataRet); - - if (!!settings) - obs_data_apply(dataRet, settings); - settings = std::move(dataRet); -} - -#define ADV_ARCHIVE_NAME "adv_archive_audio" - -#ifdef __APPLE__ -static void translate_macvth264_encoder(const char *&encoder) -{ - if (strcmp(encoder, "vt_h264_hw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264.gva"; - } else if (strcmp(encoder, "vt_h264_sw") == 0) { - encoder = "com.apple.videotoolbox.videoencoder.h264"; - } -} -#endif - -AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) -{ - const char *recType = config_get_string(main->Config(), "AdvOut", "RecType"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - const char *streamAudioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recordEncoder = config_get_string(main->Config(), "AdvOut", "RecEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); -#ifdef __APPLE__ - translate_macvth264_encoder(streamEncoder); - translate_macvth264_encoder(recordEncoder); -#endif - - ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - ffmpegRecording = ffmpegOutput && config_get_bool(main->Config(), "AdvOut", "FFOutputToFile"); - useStreamEncoder = astrcmpi(recordEncoder, "none") == 0; - useStreamAudioEncoder = astrcmpi(recAudioEncoder, "none") == 0; - - OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); - OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); - - if (ffmpegOutput) { - fileOutput = obs_output_create("ffmpeg_output", "adv_ffmpeg_output", nullptr, nullptr); - if (!fileOutput) - throw "Failed to create recording FFmpeg output " - "(advanced output)"; - } else { - bool useReplayBuffer = config_get_bool(main->Config(), "AdvOut", "RecRB"); - if (useReplayBuffer) { - OBSDataAutoRelease hotkey; - const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer"); - if (str) - hotkey = obs_data_create_from_json(str); - else - hotkey = nullptr; - - replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey); - - if (!replayBuffer) - throw "Failed to create replay buffer output " - "(simple output)"; - - signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer); - - startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this); - stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this); - replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this); - replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this); - } - - bool native_muxer = strcmp(recFormat, "hybrid_mp4") == 0; - fileOutput = obs_output_create(native_muxer ? "mp4_output" : "ffmpeg_muxer", "adv_file_output", nullptr, - nullptr); - if (!fileOutput) - throw "Failed to create recording output " - "(advanced output)"; - - if (!useStreamEncoder) { - videoRecording = obs_video_encoder_create(recordEncoder, "advanced_video_recording", - recordEncSettings, nullptr); - if (!videoRecording) - throw "Failed to create recording video " - "encoder (advanced output)"; - obs_encoder_release(videoRecording); - } - } - - videoStreaming = obs_video_encoder_create(streamEncoder, "advanced_video_stream", streamEncSettings, nullptr); - if (!videoStreaming) - throw "Failed to create streaming video encoder " - "(advanced output)"; - obs_encoder_release(videoStreaming); - - const char *rate_control = - obs_data_get_string(useStreamEncoder ? streamEncSettings : recordEncSettings, "rate_control"); - if (!rate_control) - rate_control = ""; - usesBitrate = astrcmpi(rate_control, "CBR") == 0 || astrcmpi(rate_control, "VBR") == 0 || - astrcmpi(rate_control, "ABR") == 0; - - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[19]; - snprintf(name, sizeof(name), "adv_record_audio_%d", i); - - recordTrack[i] = obs_audio_encoder_create(useStreamAudioEncoder ? streamAudioEncoder : recAudioEncoder, - name, nullptr, i, nullptr); - - if (!recordTrack[i]) { - throw "Failed to create audio encoder " - "(advanced output)"; - } - - obs_encoder_release(recordTrack[i]); - - snprintf(name, sizeof(name), "adv_stream_audio_%d", i); - streamTrack[i] = obs_audio_encoder_create(streamAudioEncoder, name, nullptr, i, nullptr); - - if (!streamTrack[i]) { - throw "Failed to create streaming audio encoders " - "(advanced output)"; - } - - obs_encoder_release(streamTrack[i]); - } - - std::string id; - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - streamAudioEnc = - obs_audio_encoder_create(streamAudioEncoder, "adv_stream_audio", nullptr, streamTrackIndex, nullptr); - if (!streamAudioEnc) - throw "Failed to create streaming audio encoder " - "(advanced output)"; - obs_encoder_release(streamAudioEnc); - - id = ""; - int vodTrack = config_get_int(main->Config(), "AdvOut", "VodTrackIndex") - 1; - streamArchiveEnc = obs_audio_encoder_create(streamAudioEncoder, ADV_ARCHIVE_NAME, nullptr, vodTrack, nullptr); - if (!streamArchiveEnc) - throw "Failed to create archive audio encoder " - "(advanced output)"; - obs_encoder_release(streamArchiveEnc); - - startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); - stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this); - recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this); - recordFileChanged.Connect(obs_output_get_signal_handler(fileOutput), "file_changed", OBSRecordFileChanged, - this); -} - -void AdvancedOutput::UpdateStreamSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - bool dynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); - - OBSData settings = GetDataFromJsonFile("streamEncoder.json"); - ApplyEncoderDefaults(settings, videoStreaming); - - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings, "bitrate"); - int keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - if (!enforceBitrate) { - blog(LOG_INFO, "User is ignoring service bitrate limits."); - obs_data_set_int(settings, "bitrate", bitrate); - } - - int enforced_keyint_sec = (int)obs_data_get_int(settings, "keyint_sec"); - if (keyint_sec != 0 && keyint_sec < enforced_keyint_sec) - obs_data_set_int(settings, "keyint_sec", keyint_sec); - } else { - blog(LOG_WARNING, "User is ignoring service settings."); - } - - if (dynBitrate && strstr(streamEncoder, "nvenc") != nullptr) - obs_data_set_bool(settings, "lookahead", false); - - video_t *video = obs_get_video(); - enum video_format format = video_output_get_format(video); - - switch (format) { - case VIDEO_FORMAT_I420: - case VIDEO_FORMAT_NV12: - case VIDEO_FORMAT_I010: - case VIDEO_FORMAT_P010: - break; - default: - obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12); - } - - obs_encoder_update(videoStreaming, settings); -} - -inline void AdvancedOutput::UpdateRecordingSettings() -{ - OBSData settings = GetDataFromJsonFile("recordEncoder.json"); - obs_encoder_update(videoRecording, settings); -} - -void AdvancedOutput::Update() -{ - UpdateStreamSettings(); - if (!useStreamEncoder && !ffmpegOutput) - UpdateRecordingSettings(); - UpdateAudioSettings(); -} - -static inline bool ServiceSupportsVodTrack(const char *service) -{ - static const char *vodTrackServices[] = {"Twitch"}; - - for (const char *vodTrackService : vodTrackServices) { - if (astrcmpi(vodTrackService, service) == 0) - return true; - } - - return false; -} - -inline bool AdvancedOutput::allowsMultiTrack() -{ - const char *protocol = nullptr; - obs_service_t *service_obj = main->GetService(); - protocol = obs_service_get_protocol(service_obj); - if (!protocol) - return false; - return astrcmpi_n(protocol, SRT_PROTOCOL, strlen(SRT_PROTOCOL)) == 0 || - astrcmpi_n(protocol, RIST_PROTOCOL, strlen(RIST_PROTOCOL)) == 0; -} - -inline void AdvancedOutput::SetupStreaming() -{ - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RescaleFilter"); - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - bool is_multitrack_output = allowsMultiTrack(); - - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - obs_encoder_set_scaled_size(videoStreaming, cx, cy); - obs_encoder_set_gpu_scale_type(videoStreaming, (obs_scale_type)rescaleFilter); - - const char *id = obs_service_get_id(main->GetService()); - if (strcmp(id, "rtmp_custom") == 0) { - OBSDataAutoRelease settings = obs_data_create(); - obs_service_apply_encoder_settings(main->GetService(), settings, nullptr); - obs_encoder_update(videoStreaming, settings); - } -} - -inline void AdvancedOutput::SetupRecording() -{ - const char *path = config_get_string(main->Config(), "AdvOut", "RecFilePath"); - const char *mux = config_get_string(main->Config(), "AdvOut", "RecMuxerCustom"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "RecRescaleRes"); - int rescaleFilter = config_get_int(main->Config(), "AdvOut", "RecRescaleFilter"); - int tracks; - - const char *recFormat = config_get_string(main->Config(), "AdvOut", "RecFormat2"); - - bool is_fragmented = strncmp(recFormat, "fragmented", 10) == 0; - bool flv = strcmp(recFormat, "flv") == 0; - - if (flv) - tracks = config_get_int(main->Config(), "AdvOut", "FLVTrack"); - else - tracks = config_get_int(main->Config(), "AdvOut", "RecTracks"); - - OBSDataAutoRelease settings = obs_data_create(); - unsigned int cx = 0; - unsigned int cy = 0; - int idx = 0; - - /* Hack to allow recordings without any audio tracks selected. It is no - * longer possible to select such a configuration in settings, but legacy - * configurations might still have this configured and we don't want to - * just break them. */ - if (tracks == 0) - tracks = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - - if (useStreamEncoder) { - obs_output_set_video_encoder(fileOutput, videoStreaming); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoStreaming); - } else { - if (rescaleFilter != OBS_SCALE_DISABLE && rescaleRes && *rescaleRes) { - if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) { - cx = 0; - cy = 0; - } - } - - obs_encoder_set_scaled_size(videoRecording, cx, cy); - obs_encoder_set_gpu_scale_type(videoRecording, (obs_scale_type)rescaleFilter); - obs_output_set_video_encoder(fileOutput, videoRecording); - if (replayBuffer) - obs_output_set_video_encoder(replayBuffer, videoRecording); - } - - if (!flv) { - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[i], idx); - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[i], idx); - idx++; - } - } - } else if (flv && tracks != 0) { - obs_output_set_audio_encoder(fileOutput, recordTrack[tracks - 1], idx); - - if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, recordTrack[tracks - 1], idx); - } - - // Use fragmented MOV/MP4 if user has not already specified custom movflags - if (is_fragmented && (!mux || strstr(mux, "movflags") == NULL)) { - string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov"; - if (mux) { - mux_frag += " "; - mux_frag += mux; - } - obs_data_set_string(settings, "muxer_settings", mux_frag.c_str()); - } else { - if (is_fragmented) - blog(LOG_WARNING, "User enabled fragmented recording, " - "but custom muxer settings contained movflags."); - obs_data_set_string(settings, "muxer_settings", mux); - } - - obs_data_set_string(settings, "path", path); - obs_output_update(fileOutput, settings); - if (replayBuffer) - obs_output_update(replayBuffer, settings); -} - -inline void AdvancedOutput::SetupFFmpeg() -{ - const char *url = config_get_string(main->Config(), "AdvOut", "FFURL"); - int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate"); - int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize"); - bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale"); - const char *rescaleRes = config_get_string(main->Config(), "AdvOut", "FFRescaleRes"); - const char *formatName = config_get_string(main->Config(), "AdvOut", "FFFormat"); - const char *mimeType = config_get_string(main->Config(), "AdvOut", "FFFormatMimeType"); - const char *muxCustom = config_get_string(main->Config(), "AdvOut", "FFMCustom"); - const char *vEncoder = config_get_string(main->Config(), "AdvOut", "FFVEncoder"); - int vEncoderId = config_get_int(main->Config(), "AdvOut", "FFVEncoderId"); - const char *vEncCustom = config_get_string(main->Config(), "AdvOut", "FFVCustom"); - int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate"); - int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes"); - const char *aEncoder = config_get_string(main->Config(), "AdvOut", "FFAEncoder"); - int aEncoderId = config_get_int(main->Config(), "AdvOut", "FFAEncoderId"); - const char *aEncCustom = config_get_string(main->Config(), "AdvOut", "FFACustom"); - - OBSDataArrayAutoRelease audio_names = obs_data_array_create(); - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - - const char *audioName = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - OBSDataAutoRelease item = obs_data_create(); - obs_data_set_string(item, "name", audioName); - obs_data_array_push_back(audio_names, item); - } - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_array(settings, "audio_names", audio_names); - obs_data_set_string(settings, "url", url); - obs_data_set_string(settings, "format_name", formatName); - obs_data_set_string(settings, "format_mime_type", mimeType); - obs_data_set_string(settings, "muxer_settings", muxCustom); - obs_data_set_int(settings, "gop_size", gopSize); - obs_data_set_int(settings, "video_bitrate", vBitrate); - obs_data_set_string(settings, "video_encoder", vEncoder); - obs_data_set_int(settings, "video_encoder_id", vEncoderId); - obs_data_set_string(settings, "video_settings", vEncCustom); - obs_data_set_int(settings, "audio_bitrate", aBitrate); - obs_data_set_string(settings, "audio_encoder", aEncoder); - obs_data_set_int(settings, "audio_encoder_id", aEncoderId); - obs_data_set_string(settings, "audio_settings", aEncCustom); - - if (rescale && rescaleRes && *rescaleRes) { - int width; - int height; - int val = sscanf(rescaleRes, "%dx%d", &width, &height); - - if (val == 2 && width && height) { - obs_data_set_int(settings, "scale_width", width); - obs_data_set_int(settings, "scale_height", height); - } - } - - obs_output_set_mixers(fileOutput, aMixes); - obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio()); - obs_output_update(fileOutput, settings); -} - -static inline void SetEncoderName(obs_encoder_t *encoder, const char *name, const char *defaultName) -{ - obs_encoder_set_name(encoder, (name && *name) ? name : defaultName); -} - -inline void AdvancedOutput::UpdateAudioSettings() -{ - bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut", "ApplyServiceSettings"); - bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - const char *audioEncoder = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - const char *recAudioEncoder = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); - - bool is_multitrack_output = allowsMultiTrack(); - - OBSDataAutoRelease settings[MAX_AUDIO_MIXES]; - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - string cfg_name = "Track"; - cfg_name += to_string((int)i + 1); - cfg_name += "Name"; - const char *name = config_get_string(main->Config(), "AdvOut", cfg_name.c_str()); - - string def_name = "Track"; - def_name += to_string((int)i + 1); - SetEncoderName(recordTrack[i], name, def_name.c_str()); - SetEncoderName(streamTrack[i], name, def_name.c_str()); - } - - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - int track = (int)(i + 1); - settings[i] = obs_data_create(); - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, recAudioEncoder)); - - obs_encoder_update(recordTrack[i], settings[i]); - - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i, audioEncoder)); - - if (!is_multitrack_output) { - if (track == streamTrackIndex || track == vodTrackIndex) { - if (applyServiceSettings) { - int bitrate = (int)obs_data_get_int(settings[i], "bitrate"); - obs_service_apply_encoder_settings(main->GetService(), nullptr, settings[i]); - - if (!enforceBitrate) - obs_data_set_int(settings[i], "bitrate", bitrate); - } - } - - if (track == streamTrackIndex) - obs_encoder_update(streamAudioEnc, settings[i]); - if (track == vodTrackIndex) - obs_encoder_update(streamArchiveEnc, settings[i]); - } else { - obs_encoder_update(streamTrack[i], settings[i]); - } - } -} - -void AdvancedOutput::SetupOutputs() -{ - obs_encoder_set_video(videoStreaming, obs_get_video()); - if (videoRecording) - obs_encoder_set_video(videoRecording, obs_get_video()); - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - obs_encoder_set_audio(streamTrack[i], obs_get_audio()); - obs_encoder_set_audio(recordTrack[i], obs_get_audio()); - } - obs_encoder_set_audio(streamAudioEnc, obs_get_audio()); - obs_encoder_set_audio(streamArchiveEnc, obs_get_audio()); - - SetupStreaming(); - - if (ffmpegOutput) - SetupFFmpeg(); - else - SetupRecording(); -} - -int AdvancedOutput::GetAudioBitrate(size_t i, const char *id) const -{ - static const char *names[] = { - "Track1Bitrate", "Track2Bitrate", "Track3Bitrate", "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", - }; - int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]); - return FindClosestAvailableAudioBitrate(id, bitrate); -} - -inline std::optional AdvancedOutput::VodTrackMixerIdx(obs_service_t *service) -{ - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex"); - bool vodTrackEnabled = config_get_bool(main->Config(), "AdvOut", "VodTrackEnabled"); - int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); - bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack"); - - const char *id = obs_service_get_id(service); - if (strcmp(id, "rtmp_custom") == 0) { - vodTrackEnabled = enableForCustomServer ? vodTrackEnabled : false; - } else { - OBSDataAutoRelease settings = obs_service_get_settings(service); - const char *service = obs_data_get_string(settings, "service"); - if (!ServiceSupportsVodTrack(service)) - vodTrackEnabled = false; - } - - if (vodTrackEnabled && streamTrackIndex != vodTrackIndex) - return {vodTrackIndex - 1}; - return std::nullopt; -} - -inline void AdvancedOutput::SetupVodTrack(obs_service_t *service) -{ - if (VodTrackMixerIdx(service).has_value()) - obs_output_set_audio_encoder(streamOutput, streamArchiveEnc, 1); - else - clear_archive_encoder(streamOutput, ADV_ARCHIVE_NAME); -} - -std::shared_future AdvancedOutput::SetupStreaming(obs_service_t *service, - SetupStreamingContinuation_t continuation) -{ - int multiTrackAudioMixes = config_get_int(main->Config(), "AdvOut", "StreamMultiTrackAudioMixes"); - - bool is_multitrack_output = allowsMultiTrack(); - - if (!useStreamEncoder || (!ffmpegOutput && !obs_output_active(fileOutput))) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - Auth *auth = main->GetAuth(); - if (auth) - auth->OnStreamConfig(); - - /* --------------------- */ - - const char *type = GetStreamOutputType(service); - if (!type) { - continuation(false); - return StartMultitrackVideoStreamingGuard::MakeReadyFuture(); - } - - const char *audio_encoder_id = config_get_string(main->Config(), "AdvOut", "AudioEncoder"); - int streamTrackIndex = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - - auto handle_multitrack_video_result = [=](std::optional multitrackVideoResult) { - if (multitrackVideoResult.has_value()) - return multitrackVideoResult.value(); - - /* XXX: this is messy and disgusting and should be refactored */ - if (outputType != type) { - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - streamOutput = obs_output_create(type, "adv_stream", nullptr, nullptr); - if (!streamOutput) { - blog(LOG_WARNING, - "Creation of stream output type '%s' " - "failed!", - type); - return false; - } - - streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting", - OBSStreamStarting, this); - streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping", - OBSStreamStopping, this); - - startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming, - this); - stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming, - this); - - outputType = type; - } - - obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - - if (!is_multitrack_output) { - obs_output_set_audio_encoder(streamOutput, streamAudioEnc, 0); - } else { - int idx = 0; - for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - if ((multiTrackAudioMixes & (1 << i)) != 0) { - obs_output_set_audio_encoder(streamOutput, streamTrack[i], idx); - idx++; - } - } - } - - return true; - }; - - return SetupMultitrackVideo(service, audio_encoder_id, static_cast(streamTrackIndex), - VodTrackMixerIdx(service), [=](std::optional res) { - continuation(handle_multitrack_video_result(res)); - }); -} - -bool AdvancedOutput::StartStreaming(obs_service_t *service) -{ - obs_output_set_service(streamOutput, service); - - bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect"); - int retryDelay = config_get_int(main->Config(), "Output", "RetryDelay"); - int maxRetries = config_get_int(main->Config(), "Output", "MaxRetries"); - bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable"); - int delaySec = config_get_int(main->Config(), "Output", "DelaySec"); - bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve"); - const char *bindIP = config_get_string(main->Config(), "Output", "BindIP"); - const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily"); -#ifdef _WIN32 - bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable"); - bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable"); -#else - bool enableNewSocketLoop = false; -#endif - bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate"); - - if (multitrackVideo && multitrackVideoActive && - !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, useDelay, enableNewSocketLoop, - enableDynBitrate)) { - multitrackVideoActive = false; - return false; - } - - bool is_rtmp = false; - obs_service_t *service_obj = main->GetService(); - const char *protocol = obs_service_get_protocol(service_obj); - if (protocol) { - if (astrcmpi_n(protocol, RTMP_PROTOCOL, strlen(RTMP_PROTOCOL)) == 0) - is_rtmp = true; - } - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, "bind_ip", bindIP); - obs_data_set_string(settings, "ip_family", ipFamily); -#ifdef _WIN32 - obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop); - obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode); -#endif - obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate); - - auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient - - obs_output_update(streamOutput, settings); - - if (!reconnect) - maxRetries = 0; - - obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0); - - obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay); - if (is_rtmp) { - SetupVodTrack(service); - } - if (obs_output_start(streamOutput)) { - if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StartedStreaming(); - return true; - } - - if (multitrackVideo && multitrackVideoActive) - multitrackVideoActive = false; - - const char *error = obs_output_get_last_error(streamOutput); - bool hasLastError = error && *error; - if (hasLastError) - lastError = error; - else - lastError = string(); - - const char *type = obs_output_get_id(streamOutput); - blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "", - hasLastError ? error : ""); - return false; -} - -bool AdvancedOutput::StartRecording() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - bool splitFile; - const char *splitFileType; - int splitFileTime; - int splitFileSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) { - UpdateRecordingSettings(); - } - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - splitFile = config_get_bool(main->Config(), "AdvOut", "RecSplitFile"); - - string strPath = GetRecordingFilename(path, recFormat, noSpace, overwriteIfExists, filenameFormat, - ffmpegRecording); - - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_string(settings, ffmpegRecording ? "url" : "path", strPath.c_str()); - - if (splitFile) { - splitFileType = config_get_string(main->Config(), "AdvOut", "RecSplitFileType"); - splitFileTime = (astrcmpi(splitFileType, "Time") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileTime") - : 0; - splitFileSize = (astrcmpi(splitFileType, "Size") == 0) - ? config_get_int(main->Config(), "AdvOut", "RecSplitFileSize") - : 0; - string ext = GetFormatExt(recFormat); - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", filenameFormat); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_bool(settings, "allow_overwrite", overwriteIfExists); - obs_data_set_bool(settings, "split_file", true); - obs_data_set_int(settings, "max_time_sec", splitFileTime * 60); - obs_data_set_int(settings, "max_size_mb", splitFileSize); - } - - obs_output_update(fileOutput, settings); - } - - if (!obs_output_start(fileOutput)) { - QString error_reason; - const char *error = obs_output_get_last_error(fileOutput); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason); - return false; - } - - return true; -} - -bool AdvancedOutput::StartReplayBuffer() -{ - const char *path; - const char *recFormat; - const char *filenameFormat; - bool noSpace = false; - bool overwriteIfExists = false; - const char *rbPrefix; - const char *rbSuffix; - int rbTime; - int rbSize; - - if (!useStreamEncoder) { - if (!ffmpegOutput) - UpdateRecordingSettings(); - } else if (!obs_output_active(StreamingOutput())) { - UpdateStreamSettings(); - } - - UpdateAudioSettings(); - - if (!Active()) - SetupOutputs(); - - if (!ffmpegOutput || ffmpegRecording) { - path = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFFilePath" : "RecFilePath"); - recFormat = config_get_string(main->Config(), "AdvOut", ffmpegRecording ? "FFExtension" : "RecFormat2"); - filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - noSpace = config_get_bool(main->Config(), "AdvOut", - ffmpegRecording ? "FFFileNameWithoutSpace" : "RecFileNameWithoutSpace"); - rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix"); - rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix"); - rbTime = config_get_int(main->Config(), "AdvOut", "RecRBTime"); - rbSize = config_get_int(main->Config(), "AdvOut", "RecRBSize"); - - string f = GetFormatString(filenameFormat, rbPrefix, rbSuffix); - string ext = GetFormatExt(recFormat); - - OBSDataAutoRelease settings = obs_data_create(); - - obs_data_set_string(settings, "directory", path); - obs_data_set_string(settings, "format", f.c_str()); - obs_data_set_string(settings, "extension", ext.c_str()); - obs_data_set_bool(settings, "allow_spaces", !noSpace); - obs_data_set_int(settings, "max_time_sec", rbTime); - obs_data_set_int(settings, "max_size_mb", usesBitrate ? 0 : rbSize); - - obs_output_update(replayBuffer, settings); - } - - if (!obs_output_start(replayBuffer)) { - QString error_reason; - const char *error = obs_output_get_last_error(replayBuffer); - if (error) - error_reason = QT_UTF8(error); - else - error_reason = QTStr("Output.StartFailedGeneric"); - QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), error_reason); - return false; - } - - return true; -} - -void AdvancedOutput::StopStreaming(bool force) -{ - auto output = StreamingOutput(); - if (force && output) - obs_output_force_stop(output); - else if (multitrackVideo && multitrackVideoActive) - multitrackVideo->StopStreaming(); - else - obs_output_stop(output); -} - -void AdvancedOutput::StopRecording(bool force) -{ - if (force) - obs_output_force_stop(fileOutput); - else - obs_output_stop(fileOutput); -} - -void AdvancedOutput::StopReplayBuffer(bool force) -{ - if (force) - obs_output_force_stop(replayBuffer); - else - obs_output_stop(replayBuffer); -} - -bool AdvancedOutput::StreamingActive() const -{ - return obs_output_active(StreamingOutput()); -} - -bool AdvancedOutput::RecordingActive() const -{ - return obs_output_active(fileOutput); -} - -bool AdvancedOutput::ReplayBufferActive() const -{ - return obs_output_active(replayBuffer); -} - -/* ------------------------------------------------------------------------ */ - -void BasicOutputHandler::SetupAutoRemux(const char *&container) -{ - bool autoRemux = config_get_bool(main->Config(), "Video", "AutoRemux"); - if (autoRemux && strcmp(container, "mp4") == 0) - container = "mkv"; -} - -std::string BasicOutputHandler::GetRecordingFilename(const char *path, const char *container, bool noSpace, - bool overwrite, const char *format, bool ffmpeg) -{ - if (!ffmpeg) - SetupAutoRemux(container); - - string dst = GetOutputFilename(path, container, noSpace, overwrite, format); - lastRecordingPath = dst; - return dst; -} - -extern std::string DeserializeConfigText(const char *text); - -std::shared_future BasicOutputHandler::SetupMultitrackVideo(obs_service_t *service, std::string audio_encoder_id, - size_t main_audio_mixer, - std::optional vod_track_mixer, - std::function)> continuation) -{ - auto start_streaming_guard = std::make_shared(); - if (!multitrackVideo) { - continuation(std::nullopt); - return start_streaming_guard->GetFuture(); - } - - multitrackVideoActive = false; - - streamDelayStarting.Disconnect(); - streamStopping.Disconnect(); - startStreaming.Disconnect(); - stopStreaming.Disconnect(); - - bool is_custom = strncmp("rtmp_custom", obs_service_get_type(service), 11) == 0; - - std::optional custom_config = std::nullopt; - if (config_get_bool(main->Config(), "Stream1", "MultitrackVideoConfigOverrideEnabled")) - custom_config = DeserializeConfigText( - config_get_string(main->Config(), "Stream1", "MultitrackVideoConfigOverride")); - - OBSDataAutoRelease settings = obs_service_get_settings(service); - QString key = obs_data_get_string(settings, "key"); - - const char *service_name = ""; - if (is_custom && obs_data_has_user_value(settings, "service_name")) { - service_name = obs_data_get_string(settings, "service_name"); - } else if (!is_custom) { - service_name = obs_data_get_string(settings, "service"); - } - - std::optional custom_rtmp_url; - std::optional use_rtmps; - auto server = obs_data_get_string(settings, "server"); - if (strncmp(server, "auto", 4) != 0) { - custom_rtmp_url = server; - } else { - QString server_ = server; - use_rtmps = server_.contains("rtmps", Qt::CaseInsensitive); - } - - auto service_custom_server = obs_data_get_bool(settings, "using_custom_server"); - if (custom_rtmp_url.has_value()) { - blog(LOG_INFO, "Using %sserver '%s'", service_custom_server ? "custom " : "", custom_rtmp_url->c_str()); - } - - auto maximum_aggregate_bitrate = - config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto") - ? std::nullopt - : std::make_optional( - config_get_int(main->Config(), "Stream1", "MultitrackVideoMaximumAggregateBitrate")); - - auto maximum_video_tracks = config_get_bool(main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracksAuto") - ? std::nullopt - : std::make_optional(config_get_int( - main->Config(), "Stream1", "MultitrackVideoMaximumVideoTracks")); - - auto stream_dump_config = GenerateMultitrackVideoStreamDumpConfig(); - - auto continue_on_main_thread = [&, start_streaming_guard, service = OBSService{service}, - continuation = - std::move(continuation)](std::optional error) { - if (error) { - OBSDataAutoRelease service_settings = obs_service_get_settings(service); - auto multitrack_video_name = QTStr("Basic.Settings.Stream.MultitrackVideoLabel"); - if (obs_data_has_user_value(service_settings, "multitrack_video_name")) { - multitrack_video_name = obs_data_get_string(service_settings, "multitrack_video_name"); - } - - multitrackVideoActive = false; - if (!error->ShowDialog(main, multitrack_video_name)) - return continuation(false); - return continuation(std::nullopt); - } - - multitrackVideoActive = true; - - auto signal_handler = multitrackVideo->StreamingSignalHandler(); - - streamDelayStarting.Connect(signal_handler, "starting", OBSStreamStarting, this); - streamStopping.Connect(signal_handler, "stopping", OBSStreamStopping, this); - - startStreaming.Connect(signal_handler, "start", OBSStartStreaming, this); - stopStreaming.Connect(signal_handler, "stop", OBSStopStreaming, this); - return continuation(true); - }; - - QThreadPool::globalInstance()->start([=, multitrackVideo = multitrackVideo.get(), - service_name = std::string{service_name}, service = OBSService{service}, - stream_dump_config = OBSData{stream_dump_config}, - start_streaming_guard = start_streaming_guard]() mutable { - std::optional error; - try { - multitrackVideo->PrepareStreaming(main, service_name.c_str(), service, custom_rtmp_url, key, - audio_encoder_id.c_str(), maximum_aggregate_bitrate, - maximum_video_tracks, custom_config, stream_dump_config, - main_audio_mixer, vod_track_mixer, use_rtmps); - } catch (const MultitrackVideoError &error_) { - error.emplace(error_); - } - - QMetaObject::invokeMethod(main, [=] { continue_on_main_thread(error); }); - }); - - return start_streaming_guard->GetFuture(); -} - -OBSDataAutoRelease BasicOutputHandler::GenerateMultitrackVideoStreamDumpConfig() -{ - auto stream_dump_enabled = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpEnabled"); - - if (!stream_dump_enabled) - return nullptr; - - const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath"); - bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists"); - bool useMP4 = config_get_bool(main->Config(), "Stream1", "MultitrackVideoStreamDumpAsMP4"); - - string f; - - OBSDataAutoRelease settings = obs_data_create(); - f = GetFormatString(filenameFormat, nullptr, nullptr); - string strPath = GetRecordingFilename(path, useMP4 ? "mp4" : "flv", noSpace, overwriteIfExists, f.c_str(), - // never remux stream dump - false); - obs_data_set_string(settings, "path", strPath.c_str()); - - if (useMP4) { - obs_data_set_bool(settings, "use_mp4", true); - obs_data_set_string(settings, "muxer_settings", "write_encoder_info=1"); - } - - return settings; -} - -BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main) -{ - return new SimpleOutput(main); -} - -BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main) -{ - return new AdvancedOutput(main); -} diff --git a/frontend/utility/SurfaceEventFilter.hpp b/frontend/utility/SurfaceEventFilter.hpp index f7c37ae9a..de6f12600 100644 --- a/frontend/utility/SurfaceEventFilter.hpp +++ b/frontend/utility/SurfaceEventFilter.hpp @@ -1,25 +1,9 @@ -#include "moc_qt-display.cpp" -#include "display-helpers.hpp" -#include -#include -#include -#include +#pragma once -#include -#include +#include -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#endif - -#if !defined(_WIN32) && !defined(__APPLE__) -#include -#endif - -#ifdef ENABLE_WAYLAND -#include -#endif +#include +#include class SurfaceEventFilter : public QObject { OBSQTDisplay *display; @@ -52,192 +36,3 @@ protected: return result; } }; - -static inline long long color_to_int(const QColor &color) -{ - auto shift = [&](unsigned val, int shift) { - return ((val & 0xff) << shift); - }; - - return shift(color.red(), 0) | shift(color.green(), 8) | shift(color.blue(), 16) | shift(color.alpha(), 24); -} - -static inline QColor rgba_to_color(uint32_t rgba) -{ - return QColor::fromRgb(rgba & 0xFF, (rgba >> 8) & 0xFF, (rgba >> 16) & 0xFF, (rgba >> 24) & 0xFF); -} - -static bool QTToGSWindow(QWindow *window, gs_window &gswindow) -{ - bool success = true; - -#ifdef _WIN32 - gswindow.hwnd = (HWND)window->winId(); -#elif __APPLE__ - gswindow.view = (id)window->winId(); -#else - switch (obs_get_nix_platform()) { - case OBS_NIX_PLATFORM_X11_EGL: - gswindow.id = window->winId(); - gswindow.display = obs_get_nix_platform_display(); - break; -#ifdef ENABLE_WAYLAND - case OBS_NIX_PLATFORM_WAYLAND: { - QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); - gswindow.display = native->nativeResourceForWindow("surface", window); - success = gswindow.display != nullptr; - break; - } -#endif - default: - success = false; - break; - } -#endif - return success; -} - -OBSQTDisplay::OBSQTDisplay(QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags) -{ - setAttribute(Qt::WA_PaintOnScreen); - setAttribute(Qt::WA_StaticContents); - setAttribute(Qt::WA_NoSystemBackground); - setAttribute(Qt::WA_OpaquePaintEvent); - setAttribute(Qt::WA_DontCreateNativeAncestors); - setAttribute(Qt::WA_NativeWindow); - - auto windowVisible = [this](bool visible) { - if (!visible) { -#if !defined(_WIN32) && !defined(__APPLE__) - display = nullptr; -#endif - return; - } - - if (!display) { - CreateDisplay(); - } else { - QSize size = GetPixelSize(this); - obs_display_resize(display, size.width(), size.height()); - } - }; - - auto screenChanged = [this](QScreen *) { - CreateDisplay(); - - QSize size = GetPixelSize(this); - obs_display_resize(display, size.width(), size.height()); - }; - - connect(windowHandle(), &QWindow::visibleChanged, windowVisible); - connect(windowHandle(), &QWindow::screenChanged, screenChanged); - - windowHandle()->installEventFilter(new SurfaceEventFilter(this)); -} - -QColor OBSQTDisplay::GetDisplayBackgroundColor() const -{ - return rgba_to_color(backgroundColor); -} - -void OBSQTDisplay::SetDisplayBackgroundColor(const QColor &color) -{ - uint32_t newBackgroundColor = (uint32_t)color_to_int(color); - - if (newBackgroundColor != backgroundColor) { - backgroundColor = newBackgroundColor; - UpdateDisplayBackgroundColor(); - } -} - -void OBSQTDisplay::UpdateDisplayBackgroundColor() -{ - obs_display_set_background_color(display, backgroundColor); -} - -void OBSQTDisplay::CreateDisplay() -{ - if (display) - return; - - if (destroying) - return; - - if (!windowHandle()->isExposed()) - return; - - QSize size = GetPixelSize(this); - - gs_init_data info = {}; - info.cx = size.width(); - info.cy = size.height(); - info.format = GS_BGRA; - info.zsformat = GS_ZS_NONE; - - if (!QTToGSWindow(windowHandle(), info.window)) - return; - - display = obs_display_create(&info, backgroundColor); - - emit DisplayCreated(this); -} - -void OBSQTDisplay::paintEvent(QPaintEvent *event) -{ - CreateDisplay(); - - QWidget::paintEvent(event); -} - -void OBSQTDisplay::moveEvent(QMoveEvent *event) -{ - QWidget::moveEvent(event); - - OnMove(); -} - -bool OBSQTDisplay::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_DISPLAYCHANGE: - OnDisplayChange(); - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSQTDisplay::resizeEvent(QResizeEvent *event) -{ - QWidget::resizeEvent(event); - - CreateDisplay(); - - if (isVisible() && display) { - QSize size = GetPixelSize(this); - obs_display_resize(display, size.width(), size.height()); - } - - emit DisplayResized(); -} - -QPaintEngine *OBSQTDisplay::paintEngine() const -{ - return nullptr; -} - -void OBSQTDisplay::OnMove() -{ - if (display) - obs_display_update_color_space(display); -} - -void OBSQTDisplay::OnDisplayChange() -{ - if (display) - obs_display_update_color_space(display); -} diff --git a/frontend/utility/VolumeMeterTimer.cpp b/frontend/utility/VolumeMeterTimer.cpp index cd00c14d7..388b9640f 100644 --- a/frontend/utility/VolumeMeterTimer.cpp +++ b/frontend/utility/VolumeMeterTimer.cpp @@ -1,1354 +1,8 @@ -#include "window-basic-main.hpp" -#include "moc_volume-control.cpp" -#include "obs-app.hpp" -#include "mute-checkbox.hpp" -#include "absolute-slider.hpp" -#include "source-label.hpp" +#include "VolumeMeterTimer.hpp" -#include -#include -#include -#include -#include -#include -#include +#include -using namespace std; - -#define FADER_PRECISION 4096.0 - -// Size of the audio indicator in pixels -#define INDICATOR_THICKNESS 3 - -// Padding on top and bottom of vertical meters -#define METER_PADDING 1 - -std::weak_ptr VolumeMeter::updateTimer; - -static inline Qt::CheckState GetCheckState(bool muted, bool unassigned) -{ - if (muted) - return Qt::Checked; - else if (unassigned) - return Qt::PartiallyChecked; - else - return Qt::Unchecked; -} - -static inline bool IsSourceUnassigned(obs_source_t *source) -{ - uint32_t mixes = (obs_source_get_audio_mixers(source) & ((1 << MAX_AUDIO_MIXES) - 1)); - obs_monitoring_type mt = obs_source_get_monitoring_type(source); - - return mixes == 0 && mt != OBS_MONITORING_TYPE_MONITOR_ONLY; -} - -static void ShowUnassignedWarning(const char *name) -{ - auto msgBox = [=]() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("VolControl.UnassignedWarning.Title")); - msgbox.setText(QTStr("VolControl.UnassignedWarning.Text").arg(name)); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); -} - -void VolControl::OBSVolumeChanged(void *data, float db) -{ - Q_UNUSED(db); - VolControl *volControl = static_cast(data); - - QMetaObject::invokeMethod(volControl, "VolumeChanged"); -} - -void VolControl::OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], - const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]) -{ - VolControl *volControl = static_cast(data); - - volControl->volMeter->setLevels(magnitude, peak, inputPeak); -} - -void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata) -{ - VolControl *volControl = static_cast(data); - bool muted = calldata_bool(calldata, "muted"); - - QMetaObject::invokeMethod(volControl, "VolumeMuted", Q_ARG(bool, muted)); -} - -void VolControl::VolumeChanged() -{ - slider->blockSignals(true); - slider->setValue((int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION)); - slider->blockSignals(false); - - updateText(); -} - -void VolControl::VolumeMuted(bool muted) -{ - bool unassigned = IsSourceUnassigned(source); - - auto newState = GetCheckState(muted, unassigned); - if (mute->checkState() != newState) - mute->setCheckState(newState); - - volMeter->muted = muted || unassigned; -} - -void VolControl::OBSMixersOrMonitoringChanged(void *data, calldata_t *) -{ - - VolControl *volControl = static_cast(data); - QMetaObject::invokeMethod(volControl, "MixersOrMonitoringChanged", Qt::QueuedConnection); -} - -void VolControl::MixersOrMonitoringChanged() -{ - bool muted = obs_source_muted(source); - bool unassigned = IsSourceUnassigned(source); - - auto newState = GetCheckState(muted, unassigned); - if (mute->checkState() != newState) - mute->setCheckState(newState); - - volMeter->muted = muted || unassigned; -} - -void VolControl::SetMuted(bool) -{ - bool checked = mute->checkState() == Qt::Checked; - bool prev = obs_source_muted(source); - obs_source_set_muted(source, checked); - bool unassigned = IsSourceUnassigned(source); - - if (!checked && unassigned) { - mute->setCheckState(Qt::PartiallyChecked); - /* Show notice about the source no being assigned to any tracks */ - bool has_shown_warning = - config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources"); - if (!has_shown_warning) - ShowUnassignedWarning(obs_source_get_name(source)); - } - - auto undo_redo = [](const std::string &uuid, bool val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_muted(source, val); - }; - - QString text = QTStr(checked ? "Undo.Volume.Mute" : "Undo.Volume.Unmute"); - - const char *name = obs_source_get_name(source); - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(text.arg(name), std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, checked), uuid, uuid); -} - -void VolControl::SliderChanged(int vol) -{ - float prev = obs_source_get_volume(source); - - obs_fader_set_deflection(obs_fader, float(vol) / FADER_PRECISION); - updateText(); - - auto undo_redo = [](const std::string &uuid, float val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_volume(source, val); - }; - - float val = obs_source_get_volume(source); - const char *name = obs_source_get_name(source); - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(QTStr("Undo.Volume.Change").arg(name), - std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, val), uuid, uuid, true); -} - -void VolControl::updateText() -{ - QString text; - float db = obs_fader_get_db(obs_fader); - - if (db < -96.0f) - text = "-inf dB"; - else - text = QString::number(db, 'f', 1).append(" dB"); - - volLabel->setText(text); - - bool muted = obs_source_muted(source); - const char *accTextLookup = muted ? "VolControl.SliderMuted" : "VolControl.SliderUnmuted"; - - QString sourceName = obs_source_get_name(source); - QString accText = QTStr(accTextLookup).arg(sourceName); - - slider->setAccessibleName(accText); -} - -void VolControl::EmitConfigClicked() -{ - emit ConfigClicked(); -} - -void VolControl::SetMeterDecayRate(qreal q) -{ - volMeter->setPeakDecayRate(q); -} - -void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType) -{ - volMeter->setPeakMeterType(peakMeterType); -} - -VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) - : source(std::move(source_)), - levelTotal(0.0f), - levelCount(0.0f), - obs_fader(obs_fader_create(OBS_FADER_LOG)), - obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)), - vertical(vertical), - contextMenu(nullptr) -{ - nameLabel = new OBSSourceLabel(source); - volLabel = new QLabel(); - mute = new MuteCheckBox(); - - volLabel->setObjectName("volLabel"); - volLabel->setAlignment(Qt::AlignCenter); - -#ifdef __APPLE__ - mute->setAttribute(Qt::WA_LayoutUsesWidgetRect); -#endif - - QString sourceName = obs_source_get_name(source); - setObjectName(sourceName); - - if (showConfig) { - config = new QPushButton(this); - config->setProperty("class", "icon-dots-vert"); - config->setAutoDefault(false); - - config->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - config->setAccessibleName(QTStr("VolControl.Properties").arg(sourceName)); - - connect(config, &QAbstractButton::clicked, this, &VolControl::EmitConfigClicked); - } - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(0); - - if (vertical) { - QHBoxLayout *nameLayout = new QHBoxLayout; - QHBoxLayout *controlLayout = new QHBoxLayout; - QHBoxLayout *volLayout = new QHBoxLayout; - QFrame *meterFrame = new QFrame; - QHBoxLayout *meterLayout = new QHBoxLayout; - - volMeter = new VolumeMeter(nullptr, obs_volmeter, true); - slider = new VolumeSlider(obs_fader, Qt::Vertical); - slider->setLayoutDirection(Qt::LeftToRight); - slider->setDisplayTicks(true); - - nameLayout->setAlignment(Qt::AlignCenter); - meterLayout->setAlignment(Qt::AlignCenter); - controlLayout->setAlignment(Qt::AlignCenter); - volLayout->setAlignment(Qt::AlignCenter); - - meterFrame->setObjectName("volMeterFrame"); - - nameLayout->setContentsMargins(0, 0, 0, 0); - nameLayout->setSpacing(0); - nameLayout->addWidget(nameLabel); - - controlLayout->setContentsMargins(0, 0, 0, 0); - controlLayout->setSpacing(0); - - // Add Headphone (audio monitoring) widget here - controlLayout->addWidget(mute); - - if (showConfig) { - controlLayout->addWidget(config); - } - - meterLayout->setContentsMargins(0, 0, 0, 0); - meterLayout->setSpacing(0); - meterLayout->addWidget(slider); - meterLayout->addWidget(volMeter); - - meterFrame->setLayout(meterLayout); - - volLayout->setContentsMargins(0, 0, 0, 0); - volLayout->setSpacing(0); - volLayout->addWidget(volLabel); - volLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum)); - - mainLayout->addItem(nameLayout); - mainLayout->addItem(volLayout); - mainLayout->addWidget(meterFrame); - mainLayout->addItem(controlLayout); - - volMeter->setFocusProxy(slider); - - // Default size can cause clipping of long names in vertical layout. - QFont font = nameLabel->font(); - QFontInfo info(font); - nameLabel->setFont(font); - - setMaximumWidth(110); - } else { - QHBoxLayout *textLayout = new QHBoxLayout; - QHBoxLayout *controlLayout = new QHBoxLayout; - QFrame *meterFrame = new QFrame; - QVBoxLayout *meterLayout = new QVBoxLayout; - QVBoxLayout *buttonLayout = new QVBoxLayout; - - volMeter = new VolumeMeter(nullptr, obs_volmeter, false); - volMeter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - - slider = new VolumeSlider(obs_fader, Qt::Horizontal); - slider->setLayoutDirection(Qt::LeftToRight); - slider->setDisplayTicks(true); - - textLayout->setContentsMargins(0, 0, 0, 0); - textLayout->addWidget(nameLabel); - textLayout->addWidget(volLabel); - textLayout->setAlignment(nameLabel, Qt::AlignLeft); - textLayout->setAlignment(volLabel, Qt::AlignRight); - - meterFrame->setObjectName("volMeterFrame"); - meterFrame->setLayout(meterLayout); - - meterLayout->setContentsMargins(0, 0, 0, 0); - meterLayout->setSpacing(0); - - meterLayout->addWidget(volMeter); - meterLayout->addWidget(slider); - - buttonLayout->setContentsMargins(0, 0, 0, 0); - buttonLayout->setSpacing(0); - - if (showConfig) { - buttonLayout->addWidget(config); - } - buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)); - buttonLayout->addWidget(mute); - - controlLayout->addItem(buttonLayout); - controlLayout->addWidget(meterFrame); - - mainLayout->addItem(textLayout); - mainLayout->addItem(controlLayout); - - volMeter->setFocusProxy(slider); - } - - setLayout(mainLayout); - - nameLabel->setText(sourceName); - - slider->setMinimum(0); - slider->setMaximum(int(FADER_PRECISION)); - - bool muted = obs_source_muted(source); - bool unassigned = IsSourceUnassigned(source); - mute->setCheckState(GetCheckState(muted, unassigned)); - volMeter->muted = muted || unassigned; - mute->setAccessibleName(QTStr("VolControl.Mute").arg(sourceName)); - obs_fader_add_callback(obs_fader, OBSVolumeChanged, this); - obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this); - - sigs.emplace_back(obs_source_get_signal_handler(source), "mute", OBSVolumeMuted, this); - sigs.emplace_back(obs_source_get_signal_handler(source), "audio_mixers", OBSMixersOrMonitoringChanged, this); - sigs.emplace_back(obs_source_get_signal_handler(source), "audio_monitoring", OBSMixersOrMonitoringChanged, - this); - - QWidget::connect(slider, &VolumeSlider::valueChanged, this, &VolControl::SliderChanged); - QWidget::connect(mute, &MuteCheckBox::clicked, this, &VolControl::SetMuted); - - obs_fader_attach_source(obs_fader, source); - obs_volmeter_attach_source(obs_volmeter, source); - - /* Call volume changed once to init the slider position and label */ - VolumeChanged(); -} - -void VolControl::EnableSlider(bool enable) -{ - slider->setEnabled(enable); -} - -VolControl::~VolControl() -{ - obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this); - obs_volmeter_remove_callback(obs_volmeter, OBSVolumeLevel, this); - - sigs.clear(); - - if (contextMenu) - contextMenu->close(); -} - -static inline QColor color_from_int(long long val) -{ - QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); - color.setAlpha(255); - - return color; -} - -QColor VolumeMeter::getBackgroundNominalColor() const -{ - return p_backgroundNominalColor; -} - -QColor VolumeMeter::getBackgroundNominalColorDisabled() const -{ - return backgroundNominalColorDisabled; -} - -void VolumeMeter::setBackgroundNominalColor(QColor c) -{ - p_backgroundNominalColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundNominalColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreen")); - } else { - backgroundNominalColor = p_backgroundNominalColor; - } -} - -void VolumeMeter::setBackgroundNominalColorDisabled(QColor c) -{ - backgroundNominalColorDisabled = std::move(c); -} - -QColor VolumeMeter::getBackgroundWarningColor() const -{ - return p_backgroundWarningColor; -} - -QColor VolumeMeter::getBackgroundWarningColorDisabled() const -{ - return backgroundWarningColorDisabled; -} - -void VolumeMeter::setBackgroundWarningColor(QColor c) -{ - p_backgroundWarningColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundWarningColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellow")); - } else { - backgroundWarningColor = p_backgroundWarningColor; - } -} - -void VolumeMeter::setBackgroundWarningColorDisabled(QColor c) -{ - backgroundWarningColorDisabled = std::move(c); -} - -QColor VolumeMeter::getBackgroundErrorColor() const -{ - return p_backgroundErrorColor; -} - -QColor VolumeMeter::getBackgroundErrorColorDisabled() const -{ - return backgroundErrorColorDisabled; -} - -void VolumeMeter::setBackgroundErrorColor(QColor c) -{ - p_backgroundErrorColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundErrorColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerRed")); - } else { - backgroundErrorColor = p_backgroundErrorColor; - } -} - -void VolumeMeter::setBackgroundErrorColorDisabled(QColor c) -{ - backgroundErrorColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundNominalColor() const -{ - return p_foregroundNominalColor; -} - -QColor VolumeMeter::getForegroundNominalColorDisabled() const -{ - return foregroundNominalColorDisabled; -} - -void VolumeMeter::setForegroundNominalColor(QColor c) -{ - p_foregroundNominalColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundNominalColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreenActive")); - } else { - foregroundNominalColor = p_foregroundNominalColor; - } -} - -void VolumeMeter::setForegroundNominalColorDisabled(QColor c) -{ - foregroundNominalColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundWarningColor() const -{ - return p_foregroundWarningColor; -} - -QColor VolumeMeter::getForegroundWarningColorDisabled() const -{ - return foregroundWarningColorDisabled; -} - -void VolumeMeter::setForegroundWarningColor(QColor c) -{ - p_foregroundWarningColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundWarningColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellowActive")); - } else { - foregroundWarningColor = p_foregroundWarningColor; - } -} - -void VolumeMeter::setForegroundWarningColorDisabled(QColor c) -{ - foregroundWarningColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundErrorColor() const -{ - return p_foregroundErrorColor; -} - -QColor VolumeMeter::getForegroundErrorColorDisabled() const -{ - return foregroundErrorColorDisabled; -} - -void VolumeMeter::setForegroundErrorColor(QColor c) -{ - p_foregroundErrorColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundErrorColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerRedActive")); - } else { - foregroundErrorColor = p_foregroundErrorColor; - } -} - -void VolumeMeter::setForegroundErrorColorDisabled(QColor c) -{ - foregroundErrorColorDisabled = std::move(c); -} - -QColor VolumeMeter::getClipColor() const -{ - return clipColor; -} - -void VolumeMeter::setClipColor(QColor c) -{ - clipColor = std::move(c); -} - -QColor VolumeMeter::getMagnitudeColor() const -{ - return magnitudeColor; -} - -void VolumeMeter::setMagnitudeColor(QColor c) -{ - magnitudeColor = std::move(c); -} - -QColor VolumeMeter::getMajorTickColor() const -{ - return majorTickColor; -} - -void VolumeMeter::setMajorTickColor(QColor c) -{ - majorTickColor = std::move(c); -} - -QColor VolumeMeter::getMinorTickColor() const -{ - return minorTickColor; -} - -void VolumeMeter::setMinorTickColor(QColor c) -{ - minorTickColor = std::move(c); -} - -int VolumeMeter::getMeterThickness() const -{ - return meterThickness; -} - -void VolumeMeter::setMeterThickness(int v) -{ - meterThickness = v; - recalculateLayout = true; -} - -qreal VolumeMeter::getMeterFontScaling() const -{ - return meterFontScaling; -} - -void VolumeMeter::setMeterFontScaling(qreal v) -{ - meterFontScaling = v; - recalculateLayout = true; -} - -void VolControl::refreshColors() -{ - volMeter->setBackgroundNominalColor(volMeter->getBackgroundNominalColor()); - volMeter->setBackgroundWarningColor(volMeter->getBackgroundWarningColor()); - volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor()); - volMeter->setForegroundNominalColor(volMeter->getForegroundNominalColor()); - volMeter->setForegroundWarningColor(volMeter->getForegroundWarningColor()); - volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor()); -} - -qreal VolumeMeter::getMinimumLevel() const -{ - return minimumLevel; -} - -void VolumeMeter::setMinimumLevel(qreal v) -{ - minimumLevel = v; -} - -qreal VolumeMeter::getWarningLevel() const -{ - return warningLevel; -} - -void VolumeMeter::setWarningLevel(qreal v) -{ - warningLevel = v; -} - -qreal VolumeMeter::getErrorLevel() const -{ - return errorLevel; -} - -void VolumeMeter::setErrorLevel(qreal v) -{ - errorLevel = v; -} - -qreal VolumeMeter::getClipLevel() const -{ - return clipLevel; -} - -void VolumeMeter::setClipLevel(qreal v) -{ - clipLevel = v; -} - -qreal VolumeMeter::getMinimumInputLevel() const -{ - return minimumInputLevel; -} - -void VolumeMeter::setMinimumInputLevel(qreal v) -{ - minimumInputLevel = v; -} - -qreal VolumeMeter::getPeakDecayRate() const -{ - return peakDecayRate; -} - -void VolumeMeter::setPeakDecayRate(qreal v) -{ - peakDecayRate = v; -} - -qreal VolumeMeter::getMagnitudeIntegrationTime() const -{ - return magnitudeIntegrationTime; -} - -void VolumeMeter::setMagnitudeIntegrationTime(qreal v) -{ - magnitudeIntegrationTime = v; -} - -qreal VolumeMeter::getPeakHoldDuration() const -{ - return peakHoldDuration; -} - -void VolumeMeter::setPeakHoldDuration(qreal v) -{ - peakHoldDuration = v; -} - -qreal VolumeMeter::getInputPeakHoldDuration() const -{ - return inputPeakHoldDuration; -} - -void VolumeMeter::setInputPeakHoldDuration(qreal v) -{ - inputPeakHoldDuration = v; -} - -void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType) -{ - obs_volmeter_set_peak_meter_type(obs_volmeter, peakMeterType); - switch (peakMeterType) { - case TRUE_PEAK_METER: - // For true-peak meters EBU has defined the Permitted Maximum, - // taking into account the accuracy of the meter and further - // processing required by lossy audio compression. - // - // The alignment level was not specified, but I've adjusted - // it compared to a sample-peak meter. Incidentally Youtube - // uses this new Alignment Level as the maximum integrated - // loudness of a video. - // - // * Permitted Maximum Level (PML) = -2.0 dBTP - // * Alignment Level (AL) = -13 dBTP - setErrorLevel(-2.0); - setWarningLevel(-13.0); - break; - - case SAMPLE_PEAK_METER: - default: - // For a sample Peak Meter EBU has the following level - // definitions, taking into account inaccuracies of this meter: - // - // * Permitted Maximum Level (PML) = -9.0 dBFS - // * Alignment Level (AL) = -20.0 dBFS - setErrorLevel(-9.0); - setWarningLevel(-20.0); - break; - } -} - -void VolumeMeter::mousePressEvent(QMouseEvent *event) -{ - setFocus(Qt::MouseFocusReason); - event->accept(); -} - -void VolumeMeter::wheelEvent(QWheelEvent *event) -{ - QApplication::sendEvent(focusProxy(), event); -} - -VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter, bool vertical) - : QWidget(parent), - obs_volmeter(obs_volmeter), - vertical(vertical) -{ - setAttribute(Qt::WA_OpaquePaintEvent, true); - - // Default meter settings, they only show if - // there is no stylesheet, do not remove. - backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green - backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow - backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red - foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green - foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow - foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red - - backgroundNominalColorDisabled.setRgb(90, 90, 90); - backgroundWarningColorDisabled.setRgb(117, 117, 117); - backgroundErrorColorDisabled.setRgb(65, 65, 65); - foregroundNominalColorDisabled.setRgb(163, 163, 163); - foregroundWarningColorDisabled.setRgb(217, 217, 217); - foregroundErrorColorDisabled.setRgb(113, 113, 113); - - clipColor.setRgb(0xff, 0xff, 0xff); // Bright white - magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black - majorTickColor.setRgb(0x00, 0x00, 0x00); // Black - minorTickColor.setRgb(0x32, 0x32, 0x32); // Dark gray - minimumLevel = -60.0; // -60 dB - warningLevel = -20.0; // -20 dB - errorLevel = -9.0; // -9 dB - clipLevel = -0.5; // -0.5 dB - minimumInputLevel = -50.0; // -50 dB - peakDecayRate = 11.76; // 20 dB / 1.7 sec - magnitudeIntegrationTime = 0.3; // 99% in 300 ms - peakHoldDuration = 20.0; // 20 seconds - inputPeakHoldDuration = 1.0; // 1 second - meterThickness = 3; // Bar thickness in pixels - meterFontScaling = 0.7; // Font size for numbers is 70% of Widget's font size - channels = (int)audio_output_get_channels(obs_get_audio()); - - doLayout(); - updateTimerRef = updateTimer.lock(); - if (!updateTimerRef) { - updateTimerRef = std::make_shared(); - updateTimerRef->setTimerType(Qt::PreciseTimer); - updateTimerRef->start(16); - updateTimer = updateTimerRef; - } - - updateTimerRef->AddVolControl(this); -} - -VolumeMeter::~VolumeMeter() -{ - updateTimerRef->RemoveVolControl(this); -} - -void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], - const float inputPeak[MAX_AUDIO_CHANNELS]) -{ - uint64_t ts = os_gettime_ns(); - QMutexLocker locker(&dataMutex); - - currentLastUpdateTime = ts; - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { - currentMagnitude[channelNr] = magnitude[channelNr]; - currentPeak[channelNr] = peak[channelNr]; - currentInputPeak[channelNr] = inputPeak[channelNr]; - } - - // In case there are more updates then redraws we must make sure - // that the ballistics of peak and hold are recalculated. - locker.unlock(); - calculateBallistics(ts); -} - -inline void VolumeMeter::resetLevels() -{ - currentLastUpdateTime = 0; - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { - currentMagnitude[channelNr] = -M_INFINITE; - currentPeak[channelNr] = -M_INFINITE; - currentInputPeak[channelNr] = -M_INFINITE; - - displayMagnitude[channelNr] = -M_INFINITE; - displayPeak[channelNr] = -M_INFINITE; - displayPeakHold[channelNr] = -M_INFINITE; - displayPeakHoldLastUpdateTime[channelNr] = 0; - displayInputPeakHold[channelNr] = -M_INFINITE; - displayInputPeakHoldLastUpdateTime[channelNr] = 0; - } -} - -bool VolumeMeter::needLayoutChange() -{ - int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); - - if (!currentNrAudioChannels) { - struct obs_audio_info oai; - obs_get_audio_info(&oai); - currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1 : 2; - } - - if (displayNrAudioChannels != currentNrAudioChannels) { - displayNrAudioChannels = currentNrAudioChannels; - recalculateLayout = true; - } - - return recalculateLayout; -} - -// When this is called from the constructor, obs_volmeter_get_nr_channels has not -// yet been called and Q_PROPERTY settings have not yet been read from the -// stylesheet. -inline void VolumeMeter::doLayout() -{ - QMutexLocker locker(&dataMutex); - - if (displayNrAudioChannels) { - int meterSize = std::floor(22 / displayNrAudioChannels); - setMeterThickness(std::clamp(meterSize, 3, 7)); - } - recalculateLayout = false; - - tickFont = font(); - QFontInfo info(tickFont); - tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling); - QFontMetrics metrics(tickFont); - if (vertical) { - // Each meter channel is meterThickness pixels wide, plus one pixel - // between channels, but not after the last. - // Add 4 pixels for ticks, space to hold our longest label in this font, - // and a few pixels before the fader. - QRect scaleBounds = metrics.boundingRect("-88"); - setMinimumSize(displayNrAudioChannels * (meterThickness + 1) - 1 + 10 + scaleBounds.width() + 2, 100); - } else { - // Each meter channel is meterThickness pixels high, plus one pixel - // between channels, but not after the last. - // Add 4 pixels for ticks, and space high enough to hold our label in - // this font, presuming that digits don't have descenders. - setMinimumSize(100, displayNrAudioChannels * (meterThickness + 1) - 1 + 4 + metrics.capHeight()); - } - - resetLevels(); -} - -inline bool VolumeMeter::detectIdle(uint64_t ts) -{ - double timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001; - if (timeSinceLastUpdate > 0.5) { - resetLevels(); - return true; - } else { - return false; - } -} - -inline void VolumeMeter::calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw) -{ - if (currentPeak[channelNr] >= displayPeak[channelNr] || isnan(displayPeak[channelNr])) { - // Attack of peak is immediate. - displayPeak[channelNr] = currentPeak[channelNr]; - } else { - // Decay of peak is 40 dB / 1.7 seconds for Fast Profile - // 20 dB / 1.7 seconds for Medium Profile (Type I PPM) - // 24 dB / 2.8 seconds for Slow Profile (Type II PPM) - float decay = float(peakDecayRate * timeSinceLastRedraw); - displayPeak[channelNr] = - std::clamp(displayPeak[channelNr] - decay, std::min(currentPeak[channelNr], 0.f), 0.f); - } - - if (currentPeak[channelNr] >= displayPeakHold[channelNr] || !isfinite(displayPeakHold[channelNr])) { - // Attack of peak-hold is immediate, but keep track - // when it was last updated. - displayPeakHold[channelNr] = currentPeak[channelNr]; - displayPeakHoldLastUpdateTime[channelNr] = ts; - } else { - // The peak and hold falls back to peak - // after 20 seconds. - qreal timeSinceLastPeak = (uint64_t)(ts - displayPeakHoldLastUpdateTime[channelNr]) * 0.000000001; - if (timeSinceLastPeak > peakHoldDuration) { - displayPeakHold[channelNr] = currentPeak[channelNr]; - displayPeakHoldLastUpdateTime[channelNr] = ts; - } - } - - if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] || - !isfinite(displayInputPeakHold[channelNr])) { - // Attack of peak-hold is immediate, but keep track - // when it was last updated. - displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; - displayInputPeakHoldLastUpdateTime[channelNr] = ts; - } else { - // The peak and hold falls back to peak after 1 second. - qreal timeSinceLastPeak = (uint64_t)(ts - displayInputPeakHoldLastUpdateTime[channelNr]) * 0.000000001; - if (timeSinceLastPeak > inputPeakHoldDuration) { - displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; - displayInputPeakHoldLastUpdateTime[channelNr] = ts; - } - } - - if (!isfinite(displayMagnitude[channelNr])) { - // The statements in the else-leg do not work with - // NaN and infinite displayMagnitude. - displayMagnitude[channelNr] = currentMagnitude[channelNr]; - } else { - // A VU meter will integrate to the new value to 99% in 300 ms. - // The calculation here is very simplified and is more accurate - // with higher frame-rate. - float attack = float((currentMagnitude[channelNr] - displayMagnitude[channelNr]) * - (timeSinceLastRedraw / magnitudeIntegrationTime) * 0.99); - displayMagnitude[channelNr] = - std::clamp(displayMagnitude[channelNr] + attack, (float)minimumLevel, 0.f); - } -} - -inline void VolumeMeter::calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw) -{ - QMutexLocker locker(&dataMutex); - - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) - calculateBallisticsForChannel(channelNr, ts, timeSinceLastRedraw); -} - -void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width, int height, float peakHold) -{ - QMutexLocker locker(&dataMutex); - QColor color; - - if (peakHold < minimumInputLevel) - color = backgroundNominalColor; - else if (peakHold < warningLevel) - color = foregroundNominalColor; - else if (peakHold < errorLevel) - color = foregroundWarningColor; - else if (peakHold <= clipLevel) - color = foregroundErrorColor; - else - color = clipColor; - - painter.fillRect(x, y, width, height, color); -} - -void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width) -{ - qreal scale = width / minimumLevel; - - painter.setFont(tickFont); - QFontMetrics metrics(tickFont); - painter.setPen(majorTickColor); - - // Draw major tick lines and numeric indicators. - for (int i = 0; i >= minimumLevel; i -= 5) { - int position = int(x + width - (i * scale) - 1); - QString str = QString::number(i); - - // Center the number on the tick, but don't overflow - QRect textBounds = metrics.boundingRect(str); - int pos; - if (i == 0) { - pos = position - textBounds.width(); - } else { - pos = position - (textBounds.width() / 2); - if (pos < 0) - pos = 0; - } - painter.drawText(pos, y + 4 + metrics.capHeight(), str); - - painter.drawLine(position, y, position, y + 2); - } -} - -void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height) -{ - qreal scale = height / minimumLevel; - - painter.setFont(tickFont); - QFontMetrics metrics(tickFont); - painter.setPen(majorTickColor); - - // Draw major tick lines and numeric indicators. - for (int i = 0; i >= minimumLevel; i -= 5) { - int position = y + int(i * scale) + METER_PADDING; - QString str = QString::number(i); - - // Center the number on the tick, but don't overflow - if (i == 0) { - painter.drawText(x + 10, position + metrics.capHeight(), str); - } else { - painter.drawText(x + 8, position + (metrics.capHeight() / 2), str); - } - - painter.drawLine(x, position, x + 2, position); - } -} - -#define CLIP_FLASH_DURATION_MS 1000 - -inline int VolumeMeter::convertToInt(float number) -{ - constexpr int min = std::numeric_limits::min(); - constexpr int max = std::numeric_limits::max(); - - // NOTE: Conversion from 'const int' to 'float' changes max value from 2147483647 to 2147483648 - if (number >= (float)max) - return max; - else if (number < min) - return min; - else - return int(number); -} - -void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold) -{ - qreal scale = width / minimumLevel; - - QMutexLocker locker(&dataMutex); - int minimumPosition = x + 0; - int maximumPosition = x + width; - int magnitudePosition = x + width - convertToInt(magnitude * scale); - int peakPosition = x + width - convertToInt(peak * scale); - int peakHoldPosition = x + width - convertToInt(peakHold * scale); - int warningPosition = x + width - convertToInt(warningLevel * scale); - int errorPosition = x + width - convertToInt(errorLevel * scale); - - int nominalLength = warningPosition - minimumPosition; - int warningLength = errorPosition - warningPosition; - int errorLength = maximumPosition - errorPosition; - locker.unlock(); - - if (clipping) { - peakPosition = maximumPosition; - } - - if (peakPosition < minimumPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < warningPosition) { - painter.fillRect(minimumPosition, y, peakPosition - minimumPosition, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(peakPosition, y, warningPosition - peakPosition, height, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < errorPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(warningPosition, y, peakPosition - warningPosition, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(peakPosition, y, errorPosition - peakPosition, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < maximumPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(errorPosition, y, peakPosition - errorPosition, height, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - painter.fillRect(peakPosition, y, maximumPosition - peakPosition, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else { - if (!clipping) { - QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, [&]() { clipping = false; }); - clipping = true; - } - - int end = errorLength + warningLength + nominalLength; - painter.fillRect(minimumPosition, y, end, height, - QBrush(muted ? foregroundErrorColorDisabled : foregroundErrorColor)); - } - - if (peakHoldPosition - 3 < minimumPosition) - ; // Peak-hold below minimum, no drawing. - else if (peakHoldPosition < warningPosition) - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - else if (peakHoldPosition < errorPosition) - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - else - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - - if (magnitudePosition - 3 >= minimumPosition) - painter.fillRect(magnitudePosition - 3, y, 3, height, magnitudeColor); -} - -void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold) -{ - qreal scale = height / minimumLevel; - - QMutexLocker locker(&dataMutex); - int minimumPosition = y + 0; - int maximumPosition = y + height; - int magnitudePosition = y + height - convertToInt(magnitude * scale); - int peakPosition = y + height - convertToInt(peak * scale); - int peakHoldPosition = y + height - convertToInt(peakHold * scale); - int warningPosition = y + height - convertToInt(warningLevel * scale); - int errorPosition = y + height - convertToInt(errorLevel * scale); - - int nominalLength = warningPosition - minimumPosition; - int warningLength = errorPosition - warningPosition; - int errorLength = maximumPosition - errorPosition; - locker.unlock(); - - if (clipping) { - peakPosition = maximumPosition; - } - - if (peakPosition < minimumPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < warningPosition) { - painter.fillRect(x, minimumPosition, width, peakPosition - minimumPosition, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, peakPosition, width, warningPosition - peakPosition, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < errorPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, warningPosition, width, peakPosition - warningPosition, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(x, peakPosition, width, errorPosition - peakPosition, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < maximumPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(x, errorPosition, width, peakPosition - errorPosition, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - painter.fillRect(x, peakPosition, width, maximumPosition - peakPosition, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else { - if (!clipping) { - QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, [&]() { clipping = false; }); - clipping = true; - } - - int end = errorLength + warningLength + nominalLength; - painter.fillRect(x, minimumPosition, width, end, - QBrush(muted ? foregroundErrorColorDisabled : foregroundErrorColor)); - } - - if (peakHoldPosition - 3 < minimumPosition) - ; // Peak-hold below minimum, no drawing. - else if (peakHoldPosition < warningPosition) - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - else if (peakHoldPosition < errorPosition) - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - else - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - - if (magnitudePosition - 3 >= minimumPosition) - painter.fillRect(x, magnitudePosition - 3, width, 3, magnitudeColor); -} - -void VolumeMeter::paintEvent(QPaintEvent *event) -{ - uint64_t ts = os_gettime_ns(); - qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; - calculateBallistics(ts, timeSinceLastRedraw); - bool idle = detectIdle(ts); - - QRect widgetRect = rect(); - int width = widgetRect.width(); - int height = widgetRect.height(); - - QPainter painter(this); - - // Paint window background color (as widget is opaque) - QColor background = palette().color(QPalette::ColorRole::Window); - painter.fillRect(event->region().boundingRect(), background); - - if (vertical) - height -= METER_PADDING * 2; - - // timerEvent requests update of the bar(s) only, so we can avoid the - // overhead of repainting the scale and labels. - if (event->region().boundingRect() != getBarRect()) { - if (needLayoutChange()) - doLayout(); - - if (vertical) { - paintVTicks(painter, displayNrAudioChannels * (meterThickness + 1) - 1, 0, - height - (INDICATOR_THICKNESS + 3)); - } else { - paintHTicks(painter, INDICATOR_THICKNESS + 3, displayNrAudioChannels * (meterThickness + 1) - 1, - width - (INDICATOR_THICKNESS + 3)); - } - } - - if (vertical) { - // Invert the Y axis to ease the math - painter.translate(0, height + METER_PADDING); - painter.scale(1, -1); - } - - for (int channelNr = 0; channelNr < displayNrAudioChannels; channelNr++) { - - int channelNrFixed = (displayNrAudioChannels == 1 && channels > 2) ? 2 : channelNr; - - if (vertical) - paintVMeter(painter, channelNr * (meterThickness + 1), INDICATOR_THICKNESS + 2, meterThickness, - height - (INDICATOR_THICKNESS + 2), displayMagnitude[channelNrFixed], - displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); - else - paintHMeter(painter, INDICATOR_THICKNESS + 2, channelNr * (meterThickness + 1), - width - (INDICATOR_THICKNESS + 2), meterThickness, displayMagnitude[channelNrFixed], - displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); - - if (idle) - continue; - - // By not drawing the input meter boxes the user can - // see that the audio stream has been stopped, without - // having too much visual impact. - if (vertical) - paintInputMeter(painter, channelNr * (meterThickness + 1), 0, meterThickness, - INDICATOR_THICKNESS, displayInputPeakHold[channelNrFixed]); - else - paintInputMeter(painter, 0, channelNr * (meterThickness + 1), INDICATOR_THICKNESS, - meterThickness, displayInputPeakHold[channelNrFixed]); - } - - lastRedrawTime = ts; -} - -QRect VolumeMeter::getBarRect() const -{ - QRect rec = rect(); - if (vertical) - rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1); - else - rec.setHeight(displayNrAudioChannels * (meterThickness + 1) - 1); - - return rec; -} - -void VolumeMeter::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::StyleChange) - recalculateLayout = true; - - QWidget::changeEvent(e); -} +#include "moc_VolumeMeterTimer.cpp" void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) { @@ -1372,136 +26,3 @@ void VolumeMeterTimer::timerEvent(QTimerEvent *) } } } - -VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent) : AbsoluteSlider(parent) -{ - fad = fader; -} - -VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, QWidget *parent) - : AbsoluteSlider(orientation, parent) -{ - fad = fader; -} - -bool VolumeSlider::getDisplayTicks() const -{ - return displayTicks; -} - -void VolumeSlider::setDisplayTicks(bool display) -{ - displayTicks = display; -} - -void VolumeSlider::paintEvent(QPaintEvent *event) -{ - if (!getDisplayTicks()) { - QSlider::paintEvent(event); - return; - } - - QPainter painter(this); - QColor tickColor(91, 98, 115, 255); - - obs_fader_conversion_t fader_db_to_def = obs_fader_db_to_def(fad); - - QStyleOptionSlider opt; - initStyleOption(&opt); - - QRect groove = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); - QRect handle = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); - - if (orientation() == Qt::Horizontal) { - const int sliderWidth = groove.width() - handle.width(); - - float tickLength = groove.height() * 1.5; - tickLength = std::max((int)tickLength + groove.height(), 8 + groove.height()); - - float yPos = groove.center().y() - (tickLength / 2) + 1; - - for (int db = -10; db >= -90; db -= 10) { - float tickValue = fader_db_to_def(db); - - float xPos = groove.left() + (tickValue * sliderWidth) + (handle.width() / 2); - painter.fillRect(xPos, yPos, 1, tickLength, tickColor); - } - } - - if (orientation() == Qt::Vertical) { - const int sliderHeight = groove.height() - handle.height(); - - float tickLength = groove.width() * 1.5; - tickLength = std::max((int)tickLength + groove.width(), 8 + groove.width()); - - float xPos = groove.center().x() - (tickLength / 2) + 1; - - for (int db = -10; db >= -96; db -= 10) { - float tickValue = fader_db_to_def(db); - - float yPos = - groove.height() + groove.top() - (tickValue * sliderHeight) - (handle.height() / 2); - painter.fillRect(xPos, yPos, tickLength, 1, tickColor); - } - } - - QSlider::paintEvent(event); -} - -VolumeAccessibleInterface::VolumeAccessibleInterface(QWidget *w) : QAccessibleWidget(w) {} - -VolumeSlider *VolumeAccessibleInterface::slider() const -{ - return qobject_cast(object()); -} - -QString VolumeAccessibleInterface::text(QAccessible::Text t) const -{ - if (slider()->isVisible()) { - switch (t) { - case QAccessible::Text::Value: - return currentValue().toString(); - default: - break; - } - } - return QAccessibleWidget::text(t); -} - -QVariant VolumeAccessibleInterface::currentValue() const -{ - QString text; - float db = obs_fader_get_db(slider()->fad); - - if (db < -96.0f) - text = "-inf dB"; - else - text = QString::number(db, 'f', 1).append(" dB"); - - return text; -} - -void VolumeAccessibleInterface::setCurrentValue(const QVariant &value) -{ - slider()->setValue(value.toInt()); -} - -QVariant VolumeAccessibleInterface::maximumValue() const -{ - return slider()->maximum(); -} - -QVariant VolumeAccessibleInterface::minimumValue() const -{ - return slider()->minimum(); -} - -QVariant VolumeAccessibleInterface::minimumStepSize() const -{ - return slider()->singleStep(); -} - -QAccessible::Role VolumeAccessibleInterface::role() const -{ - return QAccessible::Role::Slider; -} diff --git a/frontend/utility/VolumeMeterTimer.hpp b/frontend/utility/VolumeMeterTimer.hpp index 51c77f247..13590cb8f 100644 --- a/frontend/utility/VolumeMeterTimer.hpp +++ b/frontend/utility/VolumeMeterTimer.hpp @@ -1,230 +1,8 @@ #pragma once -#include -#include -#include #include -#include -#include -#include -#include -#include "absolute-slider.hpp" -class QPushButton; -class VolumeMeterTimer; -class VolumeSlider; - -class VolumeMeter : public QWidget { - Q_OBJECT - Q_PROPERTY(QColor backgroundNominalColor READ getBackgroundNominalColor WRITE setBackgroundNominalColor - DESIGNABLE true) - Q_PROPERTY(QColor backgroundWarningColor READ getBackgroundWarningColor WRITE setBackgroundWarningColor - DESIGNABLE true) - Q_PROPERTY( - QColor backgroundErrorColor READ getBackgroundErrorColor WRITE setBackgroundErrorColor DESIGNABLE true) - Q_PROPERTY(QColor foregroundNominalColor READ getForegroundNominalColor WRITE setForegroundNominalColor - DESIGNABLE true) - Q_PROPERTY(QColor foregroundWarningColor READ getForegroundWarningColor WRITE setForegroundWarningColor - DESIGNABLE true) - Q_PROPERTY( - QColor foregroundErrorColor READ getForegroundErrorColor WRITE setForegroundErrorColor DESIGNABLE true) - - Q_PROPERTY(QColor backgroundNominalColorDisabled READ getBackgroundNominalColorDisabled WRITE - setBackgroundNominalColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor backgroundWarningColorDisabled READ getBackgroundWarningColorDisabled WRITE - setBackgroundWarningColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor backgroundErrorColorDisabled READ getBackgroundErrorColorDisabled WRITE - setBackgroundErrorColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundNominalColorDisabled READ getForegroundNominalColorDisabled WRITE - setForegroundNominalColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundWarningColorDisabled READ getForegroundWarningColorDisabled WRITE - setForegroundWarningColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundErrorColorDisabled READ getForegroundErrorColorDisabled WRITE - setForegroundErrorColorDisabled DESIGNABLE true) - - Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor DESIGNABLE true) - Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE setMagnitudeColor DESIGNABLE true) - Q_PROPERTY(QColor majorTickColor READ getMajorTickColor WRITE setMajorTickColor DESIGNABLE true) - Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE setMinorTickColor DESIGNABLE true) - Q_PROPERTY(int meterThickness READ getMeterThickness WRITE setMeterThickness DESIGNABLE true) - Q_PROPERTY(qreal meterFontScaling READ getMeterFontScaling WRITE setMeterFontScaling DESIGNABLE true) - - // Levels are denoted in dBFS. - Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel DESIGNABLE true) - Q_PROPERTY(qreal warningLevel READ getWarningLevel WRITE setWarningLevel DESIGNABLE true) - Q_PROPERTY(qreal errorLevel READ getErrorLevel WRITE setErrorLevel DESIGNABLE true) - Q_PROPERTY(qreal clipLevel READ getClipLevel WRITE setClipLevel DESIGNABLE true) - Q_PROPERTY(qreal minimumInputLevel READ getMinimumInputLevel WRITE setMinimumInputLevel DESIGNABLE true) - - // Rates are denoted in dB/second. - Q_PROPERTY(qreal peakDecayRate READ getPeakDecayRate WRITE setPeakDecayRate DESIGNABLE true) - - // Time in seconds for the VU meter to integrate over. - Q_PROPERTY(qreal magnitudeIntegrationTime READ getMagnitudeIntegrationTime WRITE setMagnitudeIntegrationTime - DESIGNABLE true) - - // Duration is denoted in seconds. - Q_PROPERTY(qreal peakHoldDuration READ getPeakHoldDuration WRITE setPeakHoldDuration DESIGNABLE true) - Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration WRITE setInputPeakHoldDuration - DESIGNABLE true) - - friend class VolControl; - -private: - obs_volmeter_t *obs_volmeter; - static std::weak_ptr updateTimer; - std::shared_ptr updateTimerRef; - - inline void resetLevels(); - inline void doLayout(); - inline bool detectIdle(uint64_t ts); - inline void calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw = 0.0); - inline void calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw); - - inline int convertToInt(float number); - void paintInputMeter(QPainter &painter, int x, int y, int width, int height, float peakHold); - void paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold); - void paintHTicks(QPainter &painter, int x, int y, int width); - void paintVMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold); - void paintVTicks(QPainter &painter, int x, int y, int height); - - QMutex dataMutex; - - bool recalculateLayout = true; - uint64_t currentLastUpdateTime = 0; - float currentMagnitude[MAX_AUDIO_CHANNELS]; - float currentPeak[MAX_AUDIO_CHANNELS]; - float currentInputPeak[MAX_AUDIO_CHANNELS]; - - int displayNrAudioChannels = 0; - float displayMagnitude[MAX_AUDIO_CHANNELS]; - float displayPeak[MAX_AUDIO_CHANNELS]; - float displayPeakHold[MAX_AUDIO_CHANNELS]; - uint64_t displayPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; - float displayInputPeakHold[MAX_AUDIO_CHANNELS]; - uint64_t displayInputPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; - - QFont tickFont; - QColor backgroundNominalColor; - QColor backgroundWarningColor; - QColor backgroundErrorColor; - QColor foregroundNominalColor; - QColor foregroundWarningColor; - QColor foregroundErrorColor; - - QColor backgroundNominalColorDisabled; - QColor backgroundWarningColorDisabled; - QColor backgroundErrorColorDisabled; - QColor foregroundNominalColorDisabled; - QColor foregroundWarningColorDisabled; - QColor foregroundErrorColorDisabled; - - QColor clipColor; - QColor magnitudeColor; - QColor majorTickColor; - QColor minorTickColor; - - int meterThickness; - qreal meterFontScaling; - - qreal minimumLevel; - qreal warningLevel; - qreal errorLevel; - qreal clipLevel; - qreal minimumInputLevel; - qreal peakDecayRate; - qreal magnitudeIntegrationTime; - qreal peakHoldDuration; - qreal inputPeakHoldDuration; - - QColor p_backgroundNominalColor; - QColor p_backgroundWarningColor; - QColor p_backgroundErrorColor; - QColor p_foregroundNominalColor; - QColor p_foregroundWarningColor; - QColor p_foregroundErrorColor; - - uint64_t lastRedrawTime = 0; - int channels = 0; - bool clipping = false; - bool vertical; - bool muted = false; - -public: - explicit VolumeMeter(QWidget *parent = nullptr, obs_volmeter_t *obs_volmeter = nullptr, bool vertical = false); - ~VolumeMeter(); - - void setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], - const float inputPeak[MAX_AUDIO_CHANNELS]); - QRect getBarRect() const; - bool needLayoutChange(); - - QColor getBackgroundNominalColor() const; - void setBackgroundNominalColor(QColor c); - QColor getBackgroundWarningColor() const; - void setBackgroundWarningColor(QColor c); - QColor getBackgroundErrorColor() const; - void setBackgroundErrorColor(QColor c); - QColor getForegroundNominalColor() const; - void setForegroundNominalColor(QColor c); - QColor getForegroundWarningColor() const; - void setForegroundWarningColor(QColor c); - QColor getForegroundErrorColor() const; - void setForegroundErrorColor(QColor c); - - QColor getBackgroundNominalColorDisabled() const; - void setBackgroundNominalColorDisabled(QColor c); - QColor getBackgroundWarningColorDisabled() const; - void setBackgroundWarningColorDisabled(QColor c); - QColor getBackgroundErrorColorDisabled() const; - void setBackgroundErrorColorDisabled(QColor c); - QColor getForegroundNominalColorDisabled() const; - void setForegroundNominalColorDisabled(QColor c); - QColor getForegroundWarningColorDisabled() const; - void setForegroundWarningColorDisabled(QColor c); - QColor getForegroundErrorColorDisabled() const; - void setForegroundErrorColorDisabled(QColor c); - - QColor getClipColor() const; - void setClipColor(QColor c); - QColor getMagnitudeColor() const; - void setMagnitudeColor(QColor c); - QColor getMajorTickColor() const; - void setMajorTickColor(QColor c); - QColor getMinorTickColor() const; - void setMinorTickColor(QColor c); - int getMeterThickness() const; - void setMeterThickness(int v); - qreal getMeterFontScaling() const; - void setMeterFontScaling(qreal v); - qreal getMinimumLevel() const; - void setMinimumLevel(qreal v); - qreal getWarningLevel() const; - void setWarningLevel(qreal v); - qreal getErrorLevel() const; - void setErrorLevel(qreal v); - qreal getClipLevel() const; - void setClipLevel(qreal v); - qreal getMinimumInputLevel() const; - void setMinimumInputLevel(qreal v); - qreal getPeakDecayRate() const; - void setPeakDecayRate(qreal v); - qreal getMagnitudeIntegrationTime() const; - void setMagnitudeIntegrationTime(qreal v); - qreal getPeakHoldDuration() const; - void setPeakHoldDuration(qreal v); - qreal getInputPeakHoldDuration() const; - void setInputPeakHoldDuration(qreal v); - void setPeakMeterType(enum obs_peak_meter_type peakMeterType); - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void wheelEvent(QWheelEvent *event) override; - -protected: - void paintEvent(QPaintEvent *event) override; - void changeEvent(QEvent *e) override; -}; +class VolumeMeter; class VolumeMeterTimer : public QTimer { Q_OBJECT @@ -239,103 +17,3 @@ protected: void timerEvent(QTimerEvent *event) override; QList volumeMeters; }; - -class QLabel; -class VolumeSlider; -class MuteCheckBox; -class OBSSourceLabel; - -class VolControl : public QFrame { - Q_OBJECT - -private: - OBSSource source; - std::vector sigs; - OBSSourceLabel *nameLabel; - QLabel *volLabel; - VolumeMeter *volMeter; - VolumeSlider *slider; - MuteCheckBox *mute; - QPushButton *config = nullptr; - float levelTotal; - float levelCount; - OBSFader obs_fader; - OBSVolMeter obs_volmeter; - bool vertical; - QMenu *contextMenu; - - static void OBSVolumeChanged(void *param, float db); - static void OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], - const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]); - static void OBSVolumeMuted(void *data, calldata_t *calldata); - static void OBSMixersOrMonitoringChanged(void *data, calldata_t *); - - void EmitConfigClicked(); - -private slots: - void VolumeChanged(); - void VolumeMuted(bool muted); - void MixersOrMonitoringChanged(); - - void SetMuted(bool checked); - void SliderChanged(int vol); - void updateText(); - -signals: - void ConfigClicked(); - -public: - explicit VolControl(OBSSource source, bool showConfig = false, bool vertical = false); - ~VolControl(); - - inline obs_source_t *GetSource() const { return source; } - - void SetMeterDecayRate(qreal q); - void setPeakMeterType(enum obs_peak_meter_type peakMeterType); - - void EnableSlider(bool enable); - inline void SetContextMenu(QMenu *cm) { contextMenu = cm; } - - void refreshColors(); -}; - -class VolumeSlider : public AbsoluteSlider { - Q_OBJECT - -public: - obs_fader_t *fad; - - VolumeSlider(obs_fader_t *fader, QWidget *parent = nullptr); - VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, QWidget *parent = nullptr); - - bool getDisplayTicks() const; - void setDisplayTicks(bool display); - -private: - bool displayTicks = false; - QColor tickColor; - -protected: - virtual void paintEvent(QPaintEvent *event) override; -}; - -class VolumeAccessibleInterface : public QAccessibleWidget { - -public: - VolumeAccessibleInterface(QWidget *w); - - QVariant currentValue() const; - void setCurrentValue(const QVariant &value); - - QVariant maximumValue() const; - QVariant minimumValue() const; - - QVariant minimumStepSize() const; - -private: - VolumeSlider *slider() const; - -protected: - virtual QAccessible::Role role() const override; - virtual QString text(QAccessible::Text t) const override; -}; diff --git a/frontend/widgets/ColorSelect.cpp b/frontend/widgets/ColorSelect.cpp index 47cac8dcc..297b578b2 100644 --- a/frontend/widgets/ColorSelect.cpp +++ b/frontend/widgets/ColorSelect.cpp @@ -16,10188 +16,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} +#include "ColorSelect.hpp" ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) { ui->setupUi(this); } - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/ColorSelect.hpp b/frontend/widgets/ColorSelect.hpp index 81bb7b478..eab213409 100644 --- a/frontend/widgets/ColorSelect.hpp +++ b/frontend/widgets/ColorSelect.hpp @@ -17,153 +17,9 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "window-main.hpp" -#include "window-basic-interaction.hpp" -#include "window-basic-vcam.hpp" -#include "window-basic-properties.hpp" -#include "window-basic-transform.hpp" -#include "window-basic-adv-audio.hpp" -#include "window-basic-filters.hpp" -#include "window-missing-files.hpp" -#include "window-projector.hpp" -#include "window-basic-about.hpp" -#ifdef YOUTUBE_ENABLED -#include "window-dock-youtube-app.hpp" -#endif -#include "auth-base.hpp" -#include "log-viewer.hpp" -#include "undo-stack-obs.hpp" - -#include - -#include -#include -#include - -#include - -class QMessageBox; -class QListWidgetItem; -class VolControl; -class OBSBasicStats; -class OBSBasicVCamConfig; - -#include "ui_OBSBasic.h" #include "ui_ColorSelect.h" -#define DESKTOP_AUDIO_1 Str("DesktopAudioDevice1") -#define DESKTOP_AUDIO_2 Str("DesktopAudioDevice2") -#define AUX_AUDIO_1 Str("AuxAudioDevice1") -#define AUX_AUDIO_2 Str("AuxAudioDevice2") -#define AUX_AUDIO_3 Str("AuxAudioDevice3") -#define AUX_AUDIO_4 Str("AuxAudioDevice4") - -#define SIMPLE_ENCODER_X264 "x264" -#define SIMPLE_ENCODER_X264_LOWCPU "x264_lowcpu" -#define SIMPLE_ENCODER_QSV "qsv" -#define SIMPLE_ENCODER_QSV_AV1 "qsv_av1" -#define SIMPLE_ENCODER_NVENC "nvenc" -#define SIMPLE_ENCODER_NVENC_AV1 "nvenc_av1" -#define SIMPLE_ENCODER_NVENC_HEVC "nvenc_hevc" -#define SIMPLE_ENCODER_AMD "amd" -#define SIMPLE_ENCODER_AMD_HEVC "amd_hevc" -#define SIMPLE_ENCODER_AMD_AV1 "amd_av1" -#define SIMPLE_ENCODER_APPLE_H264 "apple_h264" -#define SIMPLE_ENCODER_APPLE_HEVC "apple_hevc" - -#define PREVIEW_EDGE_SIZE 10 - -struct BasicOutputHandler; - -enum class QtDataRole { - OBSRef = Qt::UserRole, - OBSSignals, -}; - -struct SavedProjectorInfo { - ProjectorType type; - int monitor; - std::string geometry; - std::string name; - bool alwaysOnTop; - bool alwaysOnTopOverridden; -}; - -struct SourceCopyInfo { - OBSWeakSource weak_source; - bool visible; - obs_sceneitem_crop crop; - obs_transform_info transform; - obs_blending_method blend_method; - obs_blending_type blend_mode; -}; - -struct QuickTransition { - QPushButton *button = nullptr; - OBSSource source; - obs_hotkey_id hotkey = OBS_INVALID_HOTKEY_ID; - int duration = 0; - int id = 0; - bool fadeToBlack = false; - - inline QuickTransition() {} - inline QuickTransition(OBSSource source_, int duration_, int id_, bool fadeToBlack_ = false) - : source(source_), - duration(duration_), - id(id_), - fadeToBlack(fadeToBlack_), - renamedSignal(std::make_shared(obs_source_get_signal_handler(source), "rename", - SourceRenamed, this)) - { - } - -private: - static void SourceRenamed(void *param, calldata_t *data); - std::shared_ptr renamedSignal; -}; - -struct OBSProfile { - std::string name; - std::string directoryName; - std::filesystem::path path; - std::filesystem::path profileFile; -}; - -struct OBSSceneCollection { - std::string name; - std::string fileName; - std::filesystem::path collectionFile; -}; - -struct OBSPromptResult { - bool success; - std::string promptValue; - bool optionValue; -}; - -struct OBSPromptRequest { - std::string title; - std::string prompt; - std::string promptValue; - bool withOption; - std::string optionPrompt; - bool optionValue; -}; - -using OBSPromptCallback = std::function; - -using OBSProfileCache = std::map; -using OBSSceneCollectionCache = std::map; +#include class ColorSelect : public QWidget { @@ -173,1210 +29,3 @@ public: private: std::unique_ptr ui; }; - -class OBSBasic : public OBSMainWindow { - Q_OBJECT - Q_PROPERTY(QIcon imageIcon READ GetImageIcon WRITE SetImageIcon DESIGNABLE true) - Q_PROPERTY(QIcon colorIcon READ GetColorIcon WRITE SetColorIcon DESIGNABLE true) - Q_PROPERTY(QIcon slideshowIcon READ GetSlideshowIcon WRITE SetSlideshowIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioInputIcon READ GetAudioInputIcon WRITE SetAudioInputIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioOutputIcon READ GetAudioOutputIcon WRITE SetAudioOutputIcon DESIGNABLE true) - Q_PROPERTY(QIcon desktopCapIcon READ GetDesktopCapIcon WRITE SetDesktopCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon windowCapIcon READ GetWindowCapIcon WRITE SetWindowCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon gameCapIcon READ GetGameCapIcon WRITE SetGameCapIcon DESIGNABLE true) - Q_PROPERTY(QIcon cameraIcon READ GetCameraIcon WRITE SetCameraIcon DESIGNABLE true) - Q_PROPERTY(QIcon textIcon READ GetTextIcon WRITE SetTextIcon DESIGNABLE true) - Q_PROPERTY(QIcon mediaIcon READ GetMediaIcon WRITE SetMediaIcon DESIGNABLE true) - Q_PROPERTY(QIcon browserIcon READ GetBrowserIcon WRITE SetBrowserIcon DESIGNABLE true) - Q_PROPERTY(QIcon groupIcon READ GetGroupIcon WRITE SetGroupIcon DESIGNABLE true) - Q_PROPERTY(QIcon sceneIcon READ GetSceneIcon WRITE SetSceneIcon DESIGNABLE true) - Q_PROPERTY(QIcon defaultIcon READ GetDefaultIcon WRITE SetDefaultIcon DESIGNABLE true) - Q_PROPERTY(QIcon audioProcessOutputIcon READ GetAudioProcessOutputIcon WRITE SetAudioProcessOutputIcon - DESIGNABLE true) - - friend class OBSAbout; - friend class OBSBasicPreview; - friend class OBSBasicStatusBar; - friend class OBSBasicSourceSelect; - friend class OBSBasicTransform; - friend class OBSBasicSettings; - friend class Auth; - friend class AutoConfig; - friend class AutoConfigStreamPage; - friend class RecordButton; - friend class ControlsSplitButton; - friend class ExtraBrowsersModel; - friend class ExtraBrowsersDelegate; - friend class DeviceCaptureToolbar; - friend class OBSBasicSourceSelect; - friend class OBSYoutubeActions; - friend class OBSPermissions; - friend struct BasicOutputHandler; - friend struct OBSStudioAPI; - friend class ScreenshotObj; - - enum class MoveDir { Up, Down, Left, Right }; - - enum DropType { - DropType_RawText, - DropType_Text, - DropType_Image, - DropType_Media, - DropType_Html, - DropType_Url, - }; - - enum ContextBarSize { ContextBarSize_Minimized, ContextBarSize_Reduced, ContextBarSize_Normal }; - - enum class CenterType { - Scene, - Vertical, - Horizontal, - }; - -private: - obs_frontend_callbacks *api = nullptr; - - std::shared_ptr auth; - - std::vector volumes; - - std::vector signalHandlers; - - QList> oldExtraDocks; - QStringList oldExtraDockNames; - - OBSDataAutoRelease collectionModuleData; - std::vector safeModeTransitions; - - bool loaded = false; - long disableSaving = 1; - bool projectChanged = false; - bool previewEnabled = true; - ContextBarSize contextBarSize = ContextBarSize_Normal; - - std::deque clipboard; - OBSWeakSourceAutoRelease copyFiltersSource; - bool copyVisible = true; - obs_transform_info copiedTransformInfo; - obs_sceneitem_crop copiedCropInfo; - bool hasCopiedTransform = false; - OBSWeakSourceAutoRelease copySourceTransition; - int copySourceTransitionDuration; - - bool closing = false; - bool clearingFailed = false; - - QScopedPointer devicePropertiesThread; - QScopedPointer whatsNewInitThread; - QScopedPointer updateCheckThread; - QScopedPointer introCheckThread; - QScopedPointer logUploadThread; - - QPointer interaction; - QPointer properties; - QPointer transformWindow; - QPointer advAudioWindow; - QPointer filters; - QPointer statsDock; -#ifdef YOUTUBE_ENABLED - QPointer youtubeAppDock; - uint64_t lastYouTubeAppDockCreationTime = 0; -#endif - QPointer about; - QPointer missDialog; - QPointer logView; - - QPointer cpuUsageTimer; - QPointer diskFullTimer; - - QPointer nudge_timer; - bool recent_nudge = false; - - os_cpu_usage_info_t *cpuUsageInfo = nullptr; - - OBSService service; - std::unique_ptr outputHandler; - std::shared_future setupStreamingGuard; - bool streamingStopping = false; - bool recordingStopping = false; - bool replayBufferStopping = false; - - gs_vertbuffer_t *box = nullptr; - gs_vertbuffer_t *boxLeft = nullptr; - gs_vertbuffer_t *boxTop = nullptr; - gs_vertbuffer_t *boxRight = nullptr; - gs_vertbuffer_t *boxBottom = nullptr; - gs_vertbuffer_t *circle = nullptr; - - gs_vertbuffer_t *actionSafeMargin = nullptr; - gs_vertbuffer_t *graphicsSafeMargin = nullptr; - gs_vertbuffer_t *fourByThreeSafeMargin = nullptr; - gs_vertbuffer_t *leftLine = nullptr; - gs_vertbuffer_t *topLine = nullptr; - gs_vertbuffer_t *rightLine = nullptr; - - int previewX = 0, previewY = 0; - int previewCX = 0, previewCY = 0; - float previewScale = 0.0f; - - ConfigFile activeConfiguration; - - std::vector savedProjectorsArray; - std::vector projectors; - - QPointer stats; - QPointer remux; - QPointer extraBrowsers; - QPointer importer; - - QPointer transitionButton; - - bool vcamEnabled = false; - VCamConfig vcamConfig; - - QScopedPointer trayIcon; - QPointer sysTrayStream; - QPointer sysTrayRecord; - QPointer sysTrayReplayBuffer; - QPointer sysTrayVirtualCam; - QPointer showHide; - QPointer exit; - QPointer trayMenu; - QPointer previewProjector; - QPointer studioProgramProjector; - QPointer previewProjectorSource; - QPointer previewProjectorMain; - QPointer sceneProjectorMenu; - QPointer sourceProjector; - QPointer scaleFilteringMenu; - QPointer blendingMethodMenu; - QPointer blendingModeMenu; - QPointer colorMenu; - QPointer colorWidgetAction; - QPointer colorSelect; - QPointer deinterlaceMenu; - QPointer perSceneTransitionMenu; - QPointer shortcutFilter; - QPointer renameScene; - QPointer renameSource; - - QPointer programWidget; - QPointer programLayout; - QPointer programLabel; - - QScopedPointer patronJsonThread; - std::string patronJson; - - std::atomic currentScene = nullptr; - std::optional> lastOutputResolution; - std::optional> migrationBaseResolution; - bool usingAbsoluteCoordinates = false; - - void DisableRelativeCoordinates(bool disable); - - void OnEvent(enum obs_frontend_event event); - - void UpdateMultiviewProjectorMenu(); - - void DrawBackdrop(float cx, float cy); - - void SetupEncoders(); - - void CreateFirstRunSources(); - void CreateDefaultScene(bool firstStart); - - void UpdateVolumeControlsDecayRate(); - void UpdateVolumeControlsPeakMeterType(); - void ClearVolumeControls(); - - void UploadLog(const char *subdir, const char *file, const bool crash); - - void Save(const char *file); - void LoadData(obs_data_t *data, const char *file, bool remigrate = false); - void Load(const char *file, bool remigrate = false); - - void InitHotkeys(); - void CreateHotkeys(); - void ClearHotkeys(); - - bool InitService(); - - bool InitBasicConfigDefaults(); - void InitBasicConfigDefaults2(); - bool InitBasicConfig(); - - void InitOBSCallbacks(); - - void InitPrimitives(); - - void OnFirstLoad(); - - OBSSceneItem GetSceneItem(QListWidgetItem *item); - OBSSceneItem GetCurrentSceneItem(); - - bool QueryRemoveSource(obs_source_t *source); - - void TimedCheckForUpdates(); - void CheckForUpdates(bool manualUpdate); - - void GetFPSCommon(uint32_t &num, uint32_t &den) const; - void GetFPSInteger(uint32_t &num, uint32_t &den) const; - void GetFPSFraction(uint32_t &num, uint32_t &den) const; - void GetFPSNanoseconds(uint32_t &num, uint32_t &den) const; - void GetConfigFPS(uint32_t &num, uint32_t &den) const; - - void UpdatePreviewScalingMenu(); - - void LoadSceneListOrder(obs_data_array_t *array); - obs_data_array_t *SaveSceneListOrder(); - void ChangeSceneIndex(bool relative, int idx, int invalidIdx); - - void TempFileOutput(const char *path, int vBitrate, int aBitrate); - void TempStreamOutput(const char *url, const char *key, int vBitrate, int aBitrate); - - void CloseDialogs(); - void ClearSceneData(); - void ClearProjectors(); - - void Nudge(int dist, MoveDir dir); - - OBSProjector *OpenProjector(obs_source_t *source, int monitor, ProjectorType type); - - void GetAudioSourceFilters(); - void GetAudioSourceProperties(); - void VolControlContextMenu(); - void ToggleVolControlLayout(); - void ToggleMixerLayout(bool vertical); - - void LogScenes(); - void SaveProjectNow(); - - int GetTopSelectedSourceItem(); - - QModelIndexList GetAllSelectedSourceItems(); - - obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys, replayBufHotkeys, vcamHotkeys, - togglePreviewHotkeys, contextBarHotkeys; - obs_hotkey_id forceStreamingStopHotkey, splitFileHotkey, addChapterHotkey; - - void InitDefaultTransitions(); - void InitTransition(obs_source_t *transition); - obs_source_t *FindTransition(const char *name); - OBSSource GetCurrentTransition(); - obs_data_array_t *SaveTransitions(); - void LoadTransitions(obs_data_array_t *transitions, obs_load_source_cb cb, void *private_data); - - obs_source_t *fadeTransition; - obs_source_t *cutTransition; - - void CreateProgramDisplay(); - void CreateProgramOptions(); - void AddQuickTransitionId(int id); - void AddQuickTransition(); - void AddQuickTransitionHotkey(QuickTransition *qt); - void RemoveQuickTransitionHotkey(QuickTransition *qt); - void LoadQuickTransitions(obs_data_array_t *array); - obs_data_array_t *SaveQuickTransitions(); - void ClearQuickTransitionWidgets(); - void RefreshQuickTransitions(); - void DisableQuickTransitionWidgets(); - void EnableTransitionWidgets(bool enable); - void CreateDefaultQuickTransitions(); - - void PasteShowHideTransition(obs_sceneitem_t *item, bool show, obs_source_t *tr, int duration); - QMenu *CreatePerSceneTransitionMenu(); - QMenu *CreateVisibilityTransitionMenu(bool visible); - - QuickTransition *GetQuickTransition(int id); - int GetQuickTransitionIdx(int id); - QMenu *CreateTransitionMenu(QWidget *parent, QuickTransition *qt); - void ClearQuickTransitions(); - void QuickTransitionClicked(); - void QuickTransitionChange(); - void QuickTransitionChangeDuration(int value); - void QuickTransitionRemoveClicked(); - - void SetPreviewProgramMode(bool enabled); - void ResizeProgram(uint32_t cx, uint32_t cy); - void SetCurrentScene(obs_scene_t *scene, bool force = false); - static void RenderProgram(void *data, uint32_t cx, uint32_t cy); - - std::vector quickTransitions; - QPointer programOptions; - QPointer program; - OBSWeakSource lastScene; - OBSWeakSource swapScene; - OBSWeakSource programScene; - OBSWeakSource lastProgramScene; - bool editPropertiesMode = false; - bool sceneDuplicationMode = true; - bool swapScenesMode = true; - volatile bool previewProgramMode = false; - obs_hotkey_pair_id togglePreviewProgramHotkeys = 0; - obs_hotkey_id transitionHotkey = 0; - obs_hotkey_id statsHotkey = 0; - obs_hotkey_id screenshotHotkey = 0; - obs_hotkey_id sourceScreenshotHotkey = 0; - int quickTransitionIdCounter = 1; - bool overridingTransition = false; - - int programX = 0, programY = 0; - int programCX = 0, programCY = 0; - float programScale = 0.0f; - - int disableOutputsRef = 0; - - inline void OnActivate(bool force = false); - inline void OnDeactivate(); - - void AddDropSource(const char *file, DropType image); - void AddDropURL(const char *url, QString &name, obs_data_t *settings, const obs_video_info &ovi); - void ConfirmDropUrl(const QString &url); - void dragEnterEvent(QDragEnterEvent *event) override; - void dragLeaveEvent(QDragLeaveEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dropEvent(QDropEvent *event) override; - - bool sysTrayMinimizeToTray(); - - void EnumDialogs(); - - QList visDialogs; - QList modalDialogs; - QList visMsgBoxes; - - QList visDlgPositions; - - QByteArray startingDockLayout; - - obs_data_array_t *SaveProjectors(); - void LoadSavedProjectors(obs_data_array_t *savedProjectors); - - void MacBranchesFetched(const QString &branch, bool manualUpdate); - void ReceivedIntroJson(const QString &text); - void ShowWhatsNew(const QString &url); - - void UpdatePreviewProgramIndicators(); - - QStringList extraDockNames; - QList> extraDocks; - - QStringList extraCustomDockNames; - QList> extraCustomDocks; - -#ifdef BROWSER_AVAILABLE - QPointer extraBrowserMenuDocksSeparator; - - QList> extraBrowserDocks; - QStringList extraBrowserDockNames; - QStringList extraBrowserDockTargets; - - void ClearExtraBrowserDocks(); - void LoadExtraBrowserDocks(); - void SaveExtraBrowserDocks(); - void ManageExtraBrowserDocks(); - void AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate); -#endif - - QIcon imageIcon; - QIcon colorIcon; - QIcon slideshowIcon; - QIcon audioInputIcon; - QIcon audioOutputIcon; - QIcon desktopCapIcon; - QIcon windowCapIcon; - QIcon gameCapIcon; - QIcon cameraIcon; - QIcon textIcon; - QIcon mediaIcon; - QIcon browserIcon; - QIcon groupIcon; - QIcon sceneIcon; - QIcon defaultIcon; - QIcon audioProcessOutputIcon; - - QIcon GetImageIcon() const; - QIcon GetColorIcon() const; - QIcon GetSlideshowIcon() const; - QIcon GetAudioInputIcon() const; - QIcon GetAudioOutputIcon() const; - QIcon GetDesktopCapIcon() const; - QIcon GetWindowCapIcon() const; - QIcon GetGameCapIcon() const; - QIcon GetCameraIcon() const; - QIcon GetTextIcon() const; - QIcon GetMediaIcon() const; - QIcon GetBrowserIcon() const; - QIcon GetDefaultIcon() const; - QIcon GetAudioProcessOutputIcon() const; - - QSlider *tBar; - bool tBarActive = false; - - OBSSource GetOverrideTransition(OBSSource source); - int GetOverrideTransitionDuration(OBSSource source); - - void UpdateProjectorHideCursor(); - void UpdateProjectorAlwaysOnTop(bool top); - void ResetProjectors(); - - QPointer screenshotData; - - void MoveSceneItem(enum obs_order_movement movement, const QString &action_name); - - bool autoStartBroadcast = true; - bool autoStopBroadcast = true; - bool broadcastActive = false; - bool broadcastReady = false; - QPointer youtubeStreamCheckThread; -#ifdef YOUTUBE_ENABLED - void YoutubeStreamCheck(const std::string &key); - void ShowYouTubeAutoStartWarning(); - void YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now); -#endif - void BroadcastButtonClicked(); - void SetBroadcastFlowEnabled(bool enabled); - - void UpdatePreviewSafeAreas(); - bool drawSafeAreas = false; - - void CenterSelectedSceneItems(const CenterType ¢erType); - void ShowMissingFilesDialog(obs_missing_files_t *files); - - QColor selectionColor; - QColor cropColor; - QColor hoverColor; - - QColor GetCropColor() const; - QColor GetHoverColor() const; - - void UpdatePreviewSpacingHelpers(); - bool drawSpacingHelpers = true; - - float GetDevicePixelRatio(); - void SourceToolBarActionsSetEnabled(); - - std::string lastScreenshot; - std::string lastReplay; - - void UpdatePreviewOverflowSettings(); - void UpdatePreviewScrollbars(); - - bool streamingStarting = false; - - bool recordingStarted = false; - bool isRecordingPausable = false; - bool recordingPaused = false; - - bool restartingVCam = false; - -public slots: - void DeferSaveBegin(); - void DeferSaveEnd(); - - void DisplayStreamStartError(); - - void SetupBroadcast(); - - void StartStreaming(); - void StopStreaming(); - void ForceStopStreaming(); - - void StreamDelayStarting(int sec); - void StreamDelayStopping(int sec); - - void StreamingStart(); - void StreamStopping(); - void StreamingStop(int errorcode, QString last_error); - - void StartRecording(); - void StopRecording(); - - void RecordingStart(); - void RecordStopping(); - void RecordingStop(int code, QString last_error); - void RecordingFileChanged(QString lastRecordingPath); - - void ShowReplayBufferPauseWarning(); - void StartReplayBuffer(); - void StopReplayBuffer(); - - void ReplayBufferStart(); - void ReplayBufferSave(); - void ReplayBufferSaved(); - void ReplayBufferStopping(); - void ReplayBufferStop(int code); - - void StartVirtualCam(); - void StopVirtualCam(); - - void OnVirtualCamStart(); - void OnVirtualCamStop(int code); - - void SaveProjectDeferred(); - void SaveProject(); - - void SetTransition(OBSSource transition); - void OverrideTransition(OBSSource transition); - void TransitionToScene(OBSScene scene, bool force = false); - void TransitionToScene(OBSSource scene, bool force = false, bool quickTransition = false, int quickDuration = 0, - bool black = false, bool manual = false); - void SetCurrentScene(OBSSource scene, bool force = false); - - void UpdatePatronJson(const QString &text, const QString &error); - - void ShowContextBar(); - void HideContextBar(); - void PauseRecording(); - void UnpauseRecording(); - - void UpdateEditMenu(); - -private slots: - - void on_actionMainUndo_triggered(); - void on_actionMainRedo_triggered(); - - void AddSceneItem(OBSSceneItem item); - void AddScene(OBSSource source); - void RemoveScene(OBSSource source); - void RenameSources(OBSSource source, QString newName, QString prevName); - - void ActivateAudioSource(OBSSource source); - void DeactivateAudioSource(OBSSource source); - - void DuplicateSelectedScene(); - void RemoveSelectedScene(); - - void ToggleAlwaysOnTop(); - - void ReorderSources(OBSScene scene); - void RefreshSources(OBSScene scene); - - void ProcessHotkey(obs_hotkey_id id, bool pressed); - - void AddTransition(const char *id); - void RenameTransition(OBSSource transition); - void TransitionClicked(); - void TransitionStopped(); - void TransitionFullyStopped(); - void TriggerQuickTransition(int id); - - void SetDeinterlacingMode(); - void SetDeinterlacingOrder(); - - void SetScaleFilter(); - - void SetBlendingMethod(); - void SetBlendingMode(); - - void IconActivated(QSystemTrayIcon::ActivationReason reason); - void SetShowing(bool showing); - - void ToggleShowHide(); - - void HideAudioControl(); - void UnhideAllAudioControls(); - void ToggleHideMixer(); - - void MixerRenameSource(); - - void on_vMixerScrollArea_customContextMenuRequested(); - void on_hMixerScrollArea_customContextMenuRequested(); - - void on_actionCopySource_triggered(); - void on_actionPasteRef_triggered(); - void on_actionPasteDup_triggered(); - - void on_actionCopyFilters_triggered(); - void on_actionPasteFilters_triggered(); - void AudioMixerCopyFilters(); - void AudioMixerPasteFilters(); - void SourcePasteFilters(OBSSource source, OBSSource dstSource); - - void on_previewXScrollBar_valueChanged(int value); - void on_previewYScrollBar_valueChanged(int value); - - void PreviewScalingModeChanged(int value); - - void ColorChange(); - - SourceTreeItem *GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem); - - void on_actionShowAbout_triggered(); - - void EnablePreview(); - void DisablePreview(); - - void EnablePreviewProgram(); - void DisablePreviewProgram(); - - void SceneCopyFilters(); - void ScenePasteFilters(); - - void CheckDiskSpaceRemaining(); - void OpenSavedProjector(SavedProjectorInfo *info); - - void ResetStatsHotkey(); - - void SetImageIcon(const QIcon &icon); - void SetColorIcon(const QIcon &icon); - void SetSlideshowIcon(const QIcon &icon); - void SetAudioInputIcon(const QIcon &icon); - void SetAudioOutputIcon(const QIcon &icon); - void SetDesktopCapIcon(const QIcon &icon); - void SetWindowCapIcon(const QIcon &icon); - void SetGameCapIcon(const QIcon &icon); - void SetCameraIcon(const QIcon &icon); - void SetTextIcon(const QIcon &icon); - void SetMediaIcon(const QIcon &icon); - void SetBrowserIcon(const QIcon &icon); - void SetGroupIcon(const QIcon &icon); - void SetSceneIcon(const QIcon &icon); - void SetDefaultIcon(const QIcon &icon); - void SetAudioProcessOutputIcon(const QIcon &icon); - - void TBarChanged(int value); - void TBarReleased(); - - void LockVolumeControl(bool lock); - - void UpdateVirtualCamConfig(const VCamConfig &config); - void RestartVirtualCam(const VCamConfig &config); - void RestartingVirtualCam(); - -private: - /* OBS Callbacks */ - static void SceneReordered(void *data, calldata_t *params); - static void SceneRefreshed(void *data, calldata_t *params); - static void SceneItemAdded(void *data, calldata_t *params); - static void SourceCreated(void *data, calldata_t *params); - static void SourceRemoved(void *data, calldata_t *params); - static void SourceActivated(void *data, calldata_t *params); - static void SourceDeactivated(void *data, calldata_t *params); - static void SourceAudioActivated(void *data, calldata_t *params); - static void SourceAudioDeactivated(void *data, calldata_t *params); - static void SourceRenamed(void *data, calldata_t *params); - static void RenderMain(void *data, uint32_t cx, uint32_t cy); - - void ResizePreview(uint32_t cx, uint32_t cy); - - void AddSource(const char *id); - QMenu *CreateAddSourcePopupMenu(); - void AddSourcePopupMenu(const QPoint &pos); - void copyActionsDynamicProperties(); - - static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); - - void AutoRemux(QString input, bool no_show = false); - - void UpdateIsRecordingPausable(); - - bool IsFFmpegOutputToURL() const; - bool OutputPathValid(); - void OutputPathInvalidMessage(); - - bool LowDiskSpace(); - void DiskSpaceMessage(); - - OBSSource prevFTBSource = nullptr; - - float dpi = 1.0; - -public: - OBSSource GetProgramSource(); - OBSScene GetCurrentScene(); - - void SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n); - - inline OBSSource GetCurrentSceneSource() - { - OBSScene curScene = GetCurrentScene(); - return OBSSource(obs_scene_get_source(curScene)); - } - - obs_service_t *GetService(); - void SetService(obs_service_t *service); - - int GetTransitionDuration(); - int GetTbarPosition(); - - inline bool IsPreviewProgramMode() const { return os_atomic_load_bool(&previewProgramMode); } - - inline bool VCamEnabled() const { return vcamEnabled; } - - bool Active() const; - - void ResetUI(); - int ResetVideo(); - bool ResetAudio(); - - void ResetOutputs(); - - void RefreshVolumeColors(); - - void ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel); - - void NewProject(); - void LoadProject(); - - inline void GetDisplayRect(int &x, int &y, int &cx, int &cy) - { - x = previewX; - y = previewY; - cx = previewCX; - cy = previewCY; - } - - inline bool SavingDisabled() const { return disableSaving; } - - inline double GetCPUUsage() const { return os_cpu_usage_info_query(cpuUsageInfo); } - - void SaveService(); - bool LoadService(); - - inline Auth *GetAuth() { return auth.get(); } - - inline void EnableOutputs(bool enable) - { - if (enable) { - if (--disableOutputsRef < 0) - disableOutputsRef = 0; - } else { - disableOutputsRef++; - } - } - - QMenu *AddDeinterlacingMenu(QMenu *menu, obs_source_t *source); - QMenu *AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item); - void CreateSourcePopupMenu(int idx, bool preview); - - void UpdateTitleBar(); - - void SystemTrayInit(); - void SystemTray(bool firstStarted); - - void OpenSavedProjectors(); - - void CreateInteractionWindow(obs_source_t *source); - void CreatePropertiesWindow(obs_source_t *source); - void CreateFiltersWindow(obs_source_t *source); - void CreateEditTransformWindow(obs_sceneitem_t *item); - - QAction *AddDockWidget(QDockWidget *dock); - void AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser = false); - void RemoveDockWidget(const QString &name); - bool IsDockObjectNameUsed(const QString &name); - void AddCustomDockWidget(QDockWidget *dock); - - static OBSBasic *Get(); - - const char *GetCurrentOutputPath(); - - void DeleteProjector(OBSProjector *projector); - - static QList GetProjectorMenuMonitorsFormatted(); - template - static void AddProjectorMenuMonitors(QMenu *parent, Receiver *target, void (Receiver::*slot)(Args...)) - { - auto projectors = GetProjectorMenuMonitorsFormatted(); - for (int i = 0; i < projectors.size(); i++) { - QString str = projectors[i]; - QAction *action = parent->addAction(str, target, slot); - action->setProperty("monitor", i); - } - } - - QIcon GetSourceIcon(const char *id) const; - QIcon GetGroupIcon() const; - QIcon GetSceneIcon() const; - - OBSWeakSource copyFilter; - - void ShowStatusBarMessage(const QString &message); - - static OBSData BackupScene(obs_scene_t *scene, std::vector *sources = nullptr); - void CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data); - - static inline OBSData BackupScene(obs_source_t *scene_source, std::vector *sources = nullptr) - { - obs_scene_t *scene = obs_scene_from_source(scene_source); - return BackupScene(scene, sources); - } - - void CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array); - - void SetDisplayAffinity(QWindow *window); - - QColor GetSelectionColor() const; - inline bool Closing() { return closing; } - -protected: - virtual void closeEvent(QCloseEvent *event) override; - virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; - virtual void changeEvent(QEvent *event) override; - -private slots: - void on_actionFullscreenInterface_triggered(); - - void on_actionShow_Recordings_triggered(); - void on_actionRemux_triggered(); - void on_action_Settings_triggered(); - void on_actionShowMacPermissions_triggered(); - void on_actionShowMissingFiles_triggered(); - void on_actionAdvAudioProperties_triggered(); - void on_actionMixerToolbarAdvAudio_triggered(); - void on_actionMixerToolbarMenu_triggered(); - void on_actionShowLogs_triggered(); - void on_actionUploadCurrentLog_triggered(); - void on_actionUploadLastLog_triggered(); - void on_actionViewCurrentLog_triggered(); - void on_actionCheckForUpdates_triggered(); - void on_actionRepair_triggered(); - void on_actionShowWhatsNew_triggered(); - void on_actionRestartSafe_triggered(); - - void on_actionShowCrashLogs_triggered(); - void on_actionUploadLastCrashLog_triggered(); - - void on_actionEditTransform_triggered(); - void on_actionCopyTransform_triggered(); - void on_actionPasteTransform_triggered(); - void on_actionRotate90CW_triggered(); - void on_actionRotate90CCW_triggered(); - void on_actionRotate180_triggered(); - void on_actionFlipHorizontal_triggered(); - void on_actionFlipVertical_triggered(); - void on_actionFitToScreen_triggered(); - void on_actionStretchToScreen_triggered(); - void on_actionCenterToScreen_triggered(); - void on_actionVerticalCenter_triggered(); - void on_actionHorizontalCenter_triggered(); - void on_actionSceneFilters_triggered(); - - void on_OBSBasic_customContextMenuRequested(const QPoint &pos); - - void on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *prev); - void on_scenes_customContextMenuRequested(const QPoint &pos); - void GridActionClicked(); - void on_actionSceneListMode_triggered(); - void on_actionSceneGridMode_triggered(); - void on_actionAddScene_triggered(); - void on_actionRemoveScene_triggered(); - void on_actionSceneUp_triggered(); - void on_actionSceneDown_triggered(); - void on_sources_customContextMenuRequested(const QPoint &pos); - void on_scenes_itemDoubleClicked(QListWidgetItem *item); - void on_actionAddSource_triggered(); - void on_actionRemoveSource_triggered(); - void on_actionInteract_triggered(); - void on_actionSourceProperties_triggered(); - void on_actionSourceUp_triggered(); - void on_actionSourceDown_triggered(); - - void on_actionMoveUp_triggered(); - void on_actionMoveDown_triggered(); - void on_actionMoveToTop_triggered(); - void on_actionMoveToBottom_triggered(); - - void on_actionLockPreview_triggered(); - - void on_scalingMenu_aboutToShow(); - void on_actionScaleWindow_triggered(); - void on_actionScaleCanvas_triggered(); - void on_actionScaleOutput_triggered(); - - void Screenshot(OBSSource source_ = nullptr); - void ScreenshotSelectedSource(); - void ScreenshotProgram(); - void ScreenshotScene(); - - void on_actionHelpPortal_triggered(); - void on_actionWebsite_triggered(); - void on_actionDiscord_triggered(); - void on_actionReleaseNotes_triggered(); - - void on_preview_customContextMenuRequested(); - void ProgramViewContextMenuRequested(); - void on_previewDisabledWidget_customContextMenuRequested(); - - void on_actionShowSettingsFolder_triggered(); - void on_actionShowProfileFolder_triggered(); - - void on_actionAlwaysOnTop_triggered(); - - void on_toggleListboxToolbars_toggled(bool visible); - void on_toggleContextBar_toggled(bool visible); - void on_toggleStatusBar_toggled(bool visible); - void on_toggleSourceIcons_toggled(bool visible); - - void on_transitions_currentIndexChanged(int index); - void on_transitionAdd_clicked(); - void on_transitionRemove_clicked(); - void on_transitionProps_clicked(); - void on_transitionDuration_valueChanged(); - - void ShowTransitionProperties(); - void HideTransitionProperties(); - - // Source Context Buttons - void on_sourcePropertiesButton_clicked(); - void on_sourceFiltersButton_clicked(); - void on_sourceInteractButton_clicked(); - - void on_autoConfigure_triggered(); - void on_stats_triggered(); - - void on_resetUI_triggered(); - void on_resetDocks_triggered(bool force = false); - void on_lockDocks_toggled(bool lock); - void on_multiviewProjectorWindowed_triggered(); - void on_sideDocks_toggled(bool side); - - void logUploadFinished(const QString &text, const QString &error); - void crashUploadFinished(const QString &text, const QString &error); - void openLogDialog(const QString &text, const bool crash); - - void updateCheckFinished(); - - void MoveSceneToTop(); - void MoveSceneToBottom(); - - void EditSceneName(); - void EditSceneItemName(); - - void SceneNameEdited(QWidget *editor); - - void OpenSceneFilters(); - void OpenFilters(OBSSource source = nullptr); - void OpenProperties(OBSSource source = nullptr); - void OpenInteraction(OBSSource source = nullptr); - void OpenEditTransform(OBSSceneItem item = nullptr); - - void EnablePreviewDisplay(bool enable); - void TogglePreview(); - - void OpenStudioProgramProjector(); - void OpenPreviewProjector(); - void OpenSourceProjector(); - void OpenMultiviewProjector(); - void OpenSceneProjector(); - - void OpenStudioProgramWindow(); - void OpenPreviewWindow(); - void OpenSourceWindow(); - void OpenSceneWindow(); - - void StackedMixerAreaContextMenuRequested(); - - void ResizeOutputSizeOfSource(); - - void RepairOldExtraDockName(); - void RepairCustomExtraDockName(); - - /* Stream action (start/stop) slot */ - void StreamActionTriggered(); - - /* Record action (start/stop) slot */ - void RecordActionTriggered(); - - /* Record pause (pause/unpause) slot */ - void RecordPauseToggled(); - - /* Replay Buffer action (start/stop) slot */ - void ReplayBufferActionTriggered(); - - /* Virtual Cam action (start/stop) slots */ - void VirtualCamActionTriggered(); - - void OpenVirtualCamConfig(); - - /* Studio Mode toggle slot */ - void TogglePreviewProgramMode(); - -public slots: - void on_actionResetTransform_triggered(); - - bool StreamingActive(); - bool RecordingActive(); - bool ReplayBufferActive(); - bool VirtualCamActive(); - - void ClearContextBar(); - void UpdateContextBar(bool force = false); - void UpdateContextBarDeferred(bool force = false); - void UpdateContextBarVisibility(); - -signals: - /* Streaming signals */ - void StreamingPreparing(); - void StreamingStarting(bool broadcastAutoStart); - void StreamingStarted(bool withDelay = false); - void StreamingStopping(); - void StreamingStopped(bool withDelay = false); - - /* Broadcast Flow signals */ - void BroadcastFlowEnabled(bool enabled); - void BroadcastStreamReady(bool ready); - void BroadcastStreamActive(); - void BroadcastStreamStarted(bool autoStop); - - /* Recording signals */ - void RecordingStarted(bool pausable = false); - void RecordingPaused(); - void RecordingUnpaused(); - void RecordingStopping(); - void RecordingStopped(); - - /* Replay Buffer signals */ - void ReplayBufEnabled(bool enabled); - void ReplayBufStarted(); - void ReplayBufStopping(); - void ReplayBufStopped(); - - /* Virtual Camera signals */ - void VirtualCamEnabled(); - void VirtualCamStarted(); - void VirtualCamStopped(); - - /* Studio Mode signal */ - void PreviewProgramModeChanged(bool enabled); - void CanvasResized(uint32_t width, uint32_t height); - void OutputResized(uint32_t width, uint32_t height); - - /* Preview signals */ - void PreviewXScrollBarMoved(int value); - void PreviewYScrollBarMoved(int value); - -private: - std::unique_ptr ui; - - QPointer controlsDock; - -public: - /* `undo_s` needs to be declared after `ui` to prevent an uninitialized - * warning for `ui` while initializing `undo_s`. */ - undo_stack undo_s; - - explicit OBSBasic(QWidget *parent = 0); - virtual ~OBSBasic(); - - virtual void OBSInit() override; - - virtual config_t *Config() const override; - - virtual int GetProfilePath(char *path, size_t size, const char *file) const override; - - static void InitBrowserPanelSafeBlock(); -#ifdef YOUTUBE_ENABLED - void NewYouTubeAppDock(); - void DeleteYouTubeAppDock(); - YouTubeAppDock *GetYouTubeAppDock(); -#endif - // MARK: - Generic UI Helper Functions - OBSPromptResult PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback); - - // MARK: - OBS Profile Management -private: - OBSProfileCache profiles{}; - - void SetupNewProfile(const std::string &profileName, bool useWizard = false); - void SetupDuplicateProfile(const std::string &profileName); - void SetupRenameProfile(const std::string &profileName); - - const OBSProfile &CreateProfile(const std::string &profileName); - void RemoveProfile(OBSProfile profile); - - void ChangeProfile(); - - void RefreshProfileCache(); - - void RefreshProfiles(bool refreshCache = false); - - void ActivateProfile(const OBSProfile &profile, bool reset = false); - void UpdateProfileEncoders(); - std::vector GetRestartRequirements(const ConfigFile &config) const; - void ResetProfileData(); - void CheckForSimpleModeX264Fallback(); - -public: - inline const OBSProfileCache &GetProfileCache() const noexcept { return profiles; }; - - const OBSProfile &GetCurrentProfile() const; - - std::optional GetProfileByName(const std::string &profileName) const; - std::optional GetProfileByDirectoryName(const std::string &directoryName) const; - -private slots: - void on_actionNewProfile_triggered(); - void on_actionDupProfile_triggered(); - void on_actionRenameProfile_triggered(); - void on_actionRemoveProfile_triggered(bool skipConfirmation = false); - void on_actionImportProfile_triggered(); - void on_actionExportProfile_triggered(); - -public slots: - bool CreateNewProfile(const QString &name); - bool CreateDuplicateProfile(const QString &name); - void DeleteProfile(const QString &profileName); - - // MARK: - OBS Scene Collection Management -private: - OBSSceneCollectionCache collections{}; - - void SetupNewSceneCollection(const std::string &collectionName); - void SetupDuplicateSceneCollection(const std::string &collectionName); - void SetupRenameSceneCollection(const std::string &collectionName); - - const OBSSceneCollection &CreateSceneCollection(const std::string &collectionName); - void RemoveSceneCollection(OBSSceneCollection collection); - - bool CreateDuplicateSceneCollection(const QString &name); - void DeleteSceneCollection(const QString &name); - void ChangeSceneCollection(); - - void RefreshSceneCollectionCache(); - - void RefreshSceneCollections(bool refreshCache = false); - void ActivateSceneCollection(const OBSSceneCollection &collection); - -public: - inline const OBSSceneCollectionCache &GetSceneCollectionCache() const noexcept { return collections; }; - - const OBSSceneCollection &GetCurrentSceneCollection() const; - - std::optional GetSceneCollectionByName(const std::string &collectionName) const; - std::optional GetSceneCollectionByFileName(const std::string &fileName) const; - -private slots: - void on_actionNewSceneCollection_triggered(); - void on_actionDupSceneCollection_triggered(); - void on_actionRenameSceneCollection_triggered(); - void on_actionRemoveSceneCollection_triggered(bool skipConfirmation = false); - void on_actionImportSceneCollection_triggered(); - void on_actionExportSceneCollection_triggered(); - void on_actionRemigrateSceneCollection_triggered(); - -public slots: - bool CreateNewSceneCollection(const QString &name); -}; - -extern bool cef_js_avail; - -class SceneRenameDelegate : public QStyledItemDelegate { - Q_OBJECT - -public: - SceneRenameDelegate(QObject *parent); - virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override; - -protected: - virtual bool eventFilter(QObject *editor, QEvent *event) override; -}; diff --git a/frontend/widgets/OBSBasic.cpp b/frontend/widgets/OBSBasic.cpp index 47cac8dcc..b840930ef 100644 --- a/frontend/widgets/OBSBasic.cpp +++ b/frontend/widgets/OBSBasic.cpp @@ -16,156 +16,91 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ + +#include "OBSBasic.hpp" #include "ui-config.h" +#include "ColorSelect.hpp" +#include "OBSBasicControls.hpp" +#include "OBSBasicStats.hpp" +#include "VolControl.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" #ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" +#include #endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_WIN32) || defined(WHATSNEW_ENABLED) +#include #endif +#include -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - +#include #ifdef BROWSER_AVAILABLE #include #endif +#ifdef ENABLE_WAYLAND +#include +#endif +#include -#include "ui-config.h" +#include +#include +#include -struct QCef; -struct QCefCookieManager; +#ifdef _WIN32 +#include +#endif +#include +#include -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include "Windows.h" +#endif +#include "moc_OBSBasic.cpp" + +using namespace std; + +extern bool portable_mode; +extern bool disable_3p_plugins; +extern bool opt_studio_mode; +extern bool opt_always_on_top; +extern bool opt_minimize_tray; extern std::string opt_starting_profile; extern std::string opt_starting_collection; -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace +extern bool safe_mode; +extern bool opt_start_recording; +extern bool opt_start_replaybuffer; +extern bool opt_start_virtualcam; extern volatile long insideEventLoop; +extern bool restart; -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); +extern bool EncoderAvailable(const char *encoder); -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} +extern void RegisterTwitchAuth(); +extern void RegisterRestreamAuth(); +#ifdef YOUTUBE_ENABLED +extern void RegisterYoutubeAuth(); +#endif -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} +struct QCef; -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} +extern QCef *cef; +extern bool cef_js_avail; -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} +extern void DestroyPanelCookieManager(); +extern void CheckExistingCookieId(); static void AddExtraModulePaths() { @@ -259,52 +194,7 @@ static void SetSafeModuleNames() #endif } -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif +extern void setupDockAction(QDockWidget *dock); OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) { @@ -604,980 +494,8 @@ OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new UpdatePreviewOverflowSettings(); } -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; -extern void CheckExistingCookieId(); - #ifdef __APPLE__ #define DEFAULT_CONTAINER "fragmented_mov" #elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 @@ -1858,8 +776,6 @@ bool OBSBasic::InitBasicConfigDefaults() return true; } -extern bool EncoderAvailable(const char *encoder); - void OBSBasic::InitBasicConfigDefaults2() { bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); @@ -1937,82 +853,6 @@ void OBSBasic::InitOBSCallbacks() this); } -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - #define STARTUP_SEPARATOR "==== Startup complete ===============================================" #define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" @@ -2461,398 +1301,6 @@ void OBSBasic::OnFirstLoad() on_actionViewCurrentLog_triggered(); } -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - OBSBasic::~OBSBasic() { /* clear out UI event queue */ @@ -2953,1430 +1401,6 @@ OBSBasic::~OBSBasic() #endif } -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - static inline int AttemptToResetVideo(struct obs_video_info *ovi) { return obs_reset_video(ovi); @@ -4439,18 +1463,6 @@ static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) return colorspace; } -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - int OBSBasic::ResetVideo() { if (outputHandler && outputHandler->Active()) @@ -4556,220 +1568,6 @@ bool OBSBasic::ResetAudio() return obs_reset_audio2(&ai); } -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - void OBSBasic::closeEvent(QCloseEvent *event) { /* Wait for multitrack video stream to start/finish processing in the background */ @@ -4935,2783 +1733,6 @@ void OBSBasic::changeEvent(QEvent *event) } } -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const { const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); @@ -7788,49 +1809,6 @@ config_t *OBSBasic::Config() const return activeConfiguration; } -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - void OBSBasic::UpdateEditMenu() { QModelIndexList items = GetAllSelectedSourceItems(); @@ -7901,748 +1879,6 @@ void OBSBasic::UpdateEditMenu() ui->actionHorizontalCenter->setEnabled(canTransformMultiple); } -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - void OBSBasic::UpdateTitleBar() { stringstream name; @@ -8666,1095 +1902,11 @@ void OBSBasic::UpdateTitleBar() setWindowTitle(QT_UTF8(name.str().c_str())); } -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - OBSBasic *OBSBasic::Get() { return reinterpret_cast(App()->GetMainWindow()); } -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) { if (!error.isEmpty()) @@ -9763,292 +1915,6 @@ void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) patronJson = QT_TO_UTF8(text); } -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - void OBSBasic::SetDisplayAffinity(QWindow *window) { if (!SetDisplayAffinitySupported()) @@ -10078,89 +1944,12 @@ void OBSBasic::SetDisplayAffinity(QWindow *window) #endif } -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - void OBSBasic::OnEvent(enum obs_frontend_event event) { if (api) api->on_event(event); } -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) { OBSPromptResult result; diff --git a/frontend/widgets/OBSBasic.hpp b/frontend/widgets/OBSBasic.hpp index 81bb7b478..e8169c50e 100644 --- a/frontend/widgets/OBSBasic.hpp +++ b/frontend/widgets/OBSBasic.hpp @@ -17,49 +17,51 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "window-main.hpp" -#include "window-basic-interaction.hpp" -#include "window-basic-vcam.hpp" -#include "window-basic-properties.hpp" -#include "window-basic-transform.hpp" -#include "window-basic-adv-audio.hpp" -#include "window-basic-filters.hpp" -#include "window-missing-files.hpp" -#include "window-projector.hpp" -#include "window-basic-about.hpp" -#ifdef YOUTUBE_ENABLED -#include "window-dock-youtube-app.hpp" -#endif -#include "auth-base.hpp" -#include "log-viewer.hpp" -#include "undo-stack-obs.hpp" +#include "ui_OBSBasic.h" +#include "OBSMainWindow.hpp" + +#include +#include +#include +#include +#include +#include #include +#include +Q_DECLARE_METATYPE(OBSScene); +Q_DECLARE_METATYPE(OBSSceneItem); +Q_DECLARE_METATYPE(OBSSource); + +#include #include #include #include -#include +#include -class QMessageBox; -class QListWidgetItem; +#include + +extern volatile bool recording_paused; + +class ColorSelect; +class OBSAbout; +class OBSBasicAdvAudio; +class OBSBasicFilters; +class OBSBasicInteraction; +class OBSBasicProperties; +class OBSBasicTransform; +class OBSLogViewer; +class OBSMissingFiles; +class OBSProjector; class VolControl; -class OBSBasicStats; -class OBSBasicVCamConfig; - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" +#ifdef YOUTUBE_ENABLED +class YouTubeAppDock; +#endif +class QMessageBox; +class QWidgetAction; +struct QuickTransition; #define DESKTOP_AUDIO_1 Str("DesktopAudioDevice1") #define DESKTOP_AUDIO_2 Str("DesktopAudioDevice2") @@ -83,7 +85,7 @@ class OBSBasicVCamConfig; #define PREVIEW_EDGE_SIZE 10 -struct BasicOutputHandler; +enum class ProjectorType; enum class QtDataRole { OBSRef = Qt::UserRole, @@ -108,30 +110,6 @@ struct SourceCopyInfo { obs_blending_type blend_mode; }; -struct QuickTransition { - QPushButton *button = nullptr; - OBSSource source; - obs_hotkey_id hotkey = OBS_INVALID_HOTKEY_ID; - int duration = 0; - int id = 0; - bool fadeToBlack = false; - - inline QuickTransition() {} - inline QuickTransition(OBSSource source_, int duration_, int id_, bool fadeToBlack_ = false) - : source(source_), - duration(duration_), - id(id_), - fadeToBlack(fadeToBlack_), - renamedSignal(std::make_shared(obs_source_get_signal_handler(source), "rename", - SourceRenamed, this)) - { - } - -private: - static void SourceRenamed(void *param, calldata_t *data); - std::shared_ptr renamedSignal; -}; - struct OBSProfile { std::string name; std::string directoryName; @@ -165,14 +143,60 @@ using OBSPromptCallback = std::function; using OBSProfileCache = std::map; using OBSSceneCollectionCache = std::map; -class ColorSelect : public QWidget { +template static T GetOBSRef(QListWidgetItem *item) +{ + return item->data(static_cast(QtDataRole::OBSRef)).value(); +} -public: - explicit ColorSelect(QWidget *parent = 0); +template static void SetOBSRef(QListWidgetItem *item, T &&val) +{ + item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); +} -private: - std::unique_ptr ui; -}; +static inline bool SourceMixerHidden(obs_source_t *source) +{ + OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); + bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); + + return hidden; +} + +static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) +{ + OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); + obs_data_set_bool(priv_settings, "mixer_hidden", hidden); +} + +static inline bool SourceVolumeLocked(obs_source_t *source) +{ + OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); + bool lock = obs_data_get_bool(priv_settings, "volume_locked"); + + return lock; +} + +#ifdef _WIN32 +static inline void UpdateProcessPriority() +{ + const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); + if (priority && strcmp(priority, "Normal") != 0) + SetProcessPriority(priority); +} + +static inline void ClearProcessPriority() +{ + const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); + if (priority && strcmp(priority, "Normal") != 0) + SetProcessPriority("Normal"); +} +#else +#define UpdateProcessPriority() \ + do { \ + } while (false) +#define ClearProcessPriority() \ + do { \ + } while (false) +#endif class OBSBasic : public OBSMainWindow { Q_OBJECT @@ -198,19 +222,12 @@ class OBSBasic : public OBSMainWindow { friend class OBSBasicPreview; friend class OBSBasicStatusBar; friend class OBSBasicSourceSelect; - friend class OBSBasicTransform; friend class OBSBasicSettings; friend class Auth; friend class AutoConfig; friend class AutoConfigStreamPage; - friend class RecordButton; - friend class ControlsSplitButton; friend class ExtraBrowsersModel; - friend class ExtraBrowsersDelegate; - friend class DeviceCaptureToolbar; - friend class OBSBasicSourceSelect; friend class OBSYoutubeActions; - friend class OBSPermissions; friend struct BasicOutputHandler; friend struct OBSStudioAPI; friend class ScreenshotObj; @@ -233,337 +250,102 @@ class OBSBasic : public OBSMainWindow { Vertical, Horizontal, }; - + /* ------------------------------------- + * MARK: - General + * ------------------------------------- + */ private: obs_frontend_callbacks *api = nullptr; - - std::shared_ptr auth; - - std::vector volumes; - std::vector signalHandlers; - QList> oldExtraDocks; - QStringList oldExtraDockNames; - - OBSDataAutoRelease collectionModuleData; - std::vector safeModeTransitions; - bool loaded = false; - long disableSaving = 1; - bool projectChanged = false; - bool previewEnabled = true; - ContextBarSize contextBarSize = ContextBarSize_Normal; - - std::deque clipboard; - OBSWeakSourceAutoRelease copyFiltersSource; - bool copyVisible = true; - obs_transform_info copiedTransformInfo; - obs_sceneitem_crop copiedCropInfo; - bool hasCopiedTransform = false; - OBSWeakSourceAutoRelease copySourceTransition; - int copySourceTransitionDuration; - bool closing = false; - bool clearingFailed = false; + // TODO: Remove, orphaned variable + bool copyVisible = true; + // TODO: Unused thread pointer, remove. QScopedPointer devicePropertiesThread; - QScopedPointer whatsNewInitThread; - QScopedPointer updateCheckThread; - QScopedPointer introCheckThread; + QScopedPointer logUploadThread; - QPointer interaction; - QPointer properties; - QPointer transformWindow; - QPointer advAudioWindow; - QPointer filters; - QPointer statsDock; -#ifdef YOUTUBE_ENABLED - QPointer youtubeAppDock; - uint64_t lastYouTubeAppDockCreationTime = 0; -#endif - QPointer about; - QPointer missDialog; - QPointer logView; - - QPointer cpuUsageTimer; - QPointer diskFullTimer; - - QPointer nudge_timer; - bool recent_nudge = false; - - os_cpu_usage_info_t *cpuUsageInfo = nullptr; - - OBSService service; - std::unique_ptr outputHandler; - std::shared_future setupStreamingGuard; - bool streamingStopping = false; - bool recordingStopping = false; - bool replayBufferStopping = false; - - gs_vertbuffer_t *box = nullptr; - gs_vertbuffer_t *boxLeft = nullptr; - gs_vertbuffer_t *boxTop = nullptr; - gs_vertbuffer_t *boxRight = nullptr; - gs_vertbuffer_t *boxBottom = nullptr; - gs_vertbuffer_t *circle = nullptr; - - gs_vertbuffer_t *actionSafeMargin = nullptr; - gs_vertbuffer_t *graphicsSafeMargin = nullptr; - gs_vertbuffer_t *fourByThreeSafeMargin = nullptr; - gs_vertbuffer_t *leftLine = nullptr; - gs_vertbuffer_t *topLine = nullptr; - gs_vertbuffer_t *rightLine = nullptr; - - int previewX = 0, previewY = 0; - int previewCX = 0, previewCY = 0; - float previewScale = 0.0f; - ConfigFile activeConfiguration; - std::vector savedProjectorsArray; - std::vector projectors; - - QPointer stats; - QPointer remux; - QPointer extraBrowsers; - QPointer importer; - - QPointer transitionButton; - - bool vcamEnabled = false; - VCamConfig vcamConfig; - - QScopedPointer trayIcon; - QPointer sysTrayStream; - QPointer sysTrayRecord; - QPointer sysTrayReplayBuffer; - QPointer sysTrayVirtualCam; - QPointer showHide; - QPointer exit; - QPointer trayMenu; - QPointer previewProjector; - QPointer studioProgramProjector; - QPointer previewProjectorSource; - QPointer previewProjectorMain; - QPointer sceneProjectorMenu; - QPointer sourceProjector; - QPointer scaleFilteringMenu; - QPointer blendingMethodMenu; - QPointer blendingModeMenu; - QPointer colorMenu; - QPointer colorWidgetAction; - QPointer colorSelect; - QPointer deinterlaceMenu; - QPointer perSceneTransitionMenu; - QPointer shortcutFilter; - QPointer renameScene; - QPointer renameSource; - - QPointer programWidget; - QPointer programLayout; - QPointer programLabel; - QScopedPointer patronJsonThread; std::string patronJson; - std::atomic currentScene = nullptr; - std::optional> lastOutputResolution; - std::optional> migrationBaseResolution; - bool usingAbsoluteCoordinates = false; - - void DisableRelativeCoordinates(bool disable); + std::unique_ptr ui; void OnEvent(enum obs_frontend_event event); - void UpdateMultiviewProjectorMenu(); - - void DrawBackdrop(float cx, float cy); - - void SetupEncoders(); - - void CreateFirstRunSources(); - void CreateDefaultScene(bool firstStart); - - void UpdateVolumeControlsDecayRate(); - void UpdateVolumeControlsPeakMeterType(); - void ClearVolumeControls(); - - void UploadLog(const char *subdir, const char *file, const bool crash); - - void Save(const char *file); - void LoadData(obs_data_t *data, const char *file, bool remigrate = false); - void Load(const char *file, bool remigrate = false); - - void InitHotkeys(); - void CreateHotkeys(); - void ClearHotkeys(); - - bool InitService(); - bool InitBasicConfigDefaults(); void InitBasicConfigDefaults2(); bool InitBasicConfig(); void InitOBSCallbacks(); - void InitPrimitives(); - void OnFirstLoad(); - OBSSceneItem GetSceneItem(QListWidgetItem *item); - OBSSceneItem GetCurrentSceneItem(); - - bool QueryRemoveSource(obs_source_t *source); - - void TimedCheckForUpdates(); - void CheckForUpdates(bool manualUpdate); - void GetFPSCommon(uint32_t &num, uint32_t &den) const; void GetFPSInteger(uint32_t &num, uint32_t &den) const; void GetFPSFraction(uint32_t &num, uint32_t &den) const; void GetFPSNanoseconds(uint32_t &num, uint32_t &den) const; void GetConfigFPS(uint32_t &num, uint32_t &den) const; - void UpdatePreviewScalingMenu(); + OBSPromptResult PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback); - void LoadSceneListOrder(obs_data_array_t *array); - obs_data_array_t *SaveSceneListOrder(); - void ChangeSceneIndex(bool relative, int idx, int invalidIdx); + // TODO: Remove, orphaned instance method + void NewProject(); + // TODO: Remove, orphaned instance method + void LoadProject(); - void TempFileOutput(const char *path, int vBitrate, int aBitrate); - void TempStreamOutput(const char *url, const char *key, int vBitrate, int aBitrate); +public slots: + void UpdatePatronJson(const QString &text, const QString &error); + void UpdateEditMenu(); - void CloseDialogs(); - void ClearSceneData(); - void ClearProjectors(); +public: + /* `undo_s` needs to be declared after `ui` to prevent an uninitialized + * warning for `ui` while initializing `undo_s`. */ + undo_stack undo_s; - void Nudge(int dist, MoveDir dir); + explicit OBSBasic(QWidget *parent = 0); + virtual ~OBSBasic(); - OBSProjector *OpenProjector(obs_source_t *source, int monitor, ProjectorType type); + virtual void OBSInit() override; - void GetAudioSourceFilters(); - void GetAudioSourceProperties(); - void VolControlContextMenu(); - void ToggleVolControlLayout(); - void ToggleMixerLayout(bool vertical); + virtual config_t *Config() const override; - void LogScenes(); - void SaveProjectNow(); + int ResetVideo(); + bool ResetAudio(); - int GetTopSelectedSourceItem(); + void UpdateTitleBar(); - QModelIndexList GetAllSelectedSourceItems(); + static OBSBasic *Get(); - obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys, replayBufHotkeys, vcamHotkeys, - togglePreviewHotkeys, contextBarHotkeys; - obs_hotkey_id forceStreamingStopHotkey, splitFileHotkey, addChapterHotkey; + void SetDisplayAffinity(QWindow *window); - void InitDefaultTransitions(); - void InitTransition(obs_source_t *transition); - obs_source_t *FindTransition(const char *name); - OBSSource GetCurrentTransition(); - obs_data_array_t *SaveTransitions(); - void LoadTransitions(obs_data_array_t *transitions, obs_load_source_cb cb, void *private_data); + inline bool Closing() { return closing; } - obs_source_t *fadeTransition; - obs_source_t *cutTransition; +protected: + virtual void closeEvent(QCloseEvent *event) override; + virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; + virtual void changeEvent(QEvent *event) override; - void CreateProgramDisplay(); - void CreateProgramOptions(); - void AddQuickTransitionId(int id); - void AddQuickTransition(); - void AddQuickTransitionHotkey(QuickTransition *qt); - void RemoveQuickTransitionHotkey(QuickTransition *qt); - void LoadQuickTransitions(obs_data_array_t *array); - obs_data_array_t *SaveQuickTransitions(); - void ClearQuickTransitionWidgets(); - void RefreshQuickTransitions(); - void DisableQuickTransitionWidgets(); - void EnableTransitionWidgets(bool enable); - void CreateDefaultQuickTransitions(); + /* ------------------------------------- + * MARK: - OAuth + * ------------------------------------- + */ +private: + std::shared_ptr auth; - void PasteShowHideTransition(obs_sceneitem_t *item, bool show, obs_source_t *tr, int duration); - QMenu *CreatePerSceneTransitionMenu(); - QMenu *CreateVisibilityTransitionMenu(bool visible); +public: + inline Auth *GetAuth() { return auth.get(); } - QuickTransition *GetQuickTransition(int id); - int GetQuickTransitionIdx(int id); - QMenu *CreateTransitionMenu(QWidget *parent, QuickTransition *qt); - void ClearQuickTransitions(); - void QuickTransitionClicked(); - void QuickTransitionChange(); - void QuickTransitionChangeDuration(int value); - void QuickTransitionRemoveClicked(); - - void SetPreviewProgramMode(bool enabled); - void ResizeProgram(uint32_t cx, uint32_t cy); - void SetCurrentScene(obs_scene_t *scene, bool force = false); - static void RenderProgram(void *data, uint32_t cx, uint32_t cy); - - std::vector quickTransitions; - QPointer programOptions; - QPointer program; - OBSWeakSource lastScene; - OBSWeakSource swapScene; - OBSWeakSource programScene; - OBSWeakSource lastProgramScene; - bool editPropertiesMode = false; - bool sceneDuplicationMode = true; - bool swapScenesMode = true; - volatile bool previewProgramMode = false; - obs_hotkey_pair_id togglePreviewProgramHotkeys = 0; - obs_hotkey_id transitionHotkey = 0; - obs_hotkey_id statsHotkey = 0; - obs_hotkey_id screenshotHotkey = 0; - obs_hotkey_id sourceScreenshotHotkey = 0; - int quickTransitionIdCounter = 1; - bool overridingTransition = false; - - int programX = 0, programY = 0; - int programCX = 0, programCY = 0; - float programScale = 0.0f; - - int disableOutputsRef = 0; - - inline void OnActivate(bool force = false); - inline void OnDeactivate(); - - void AddDropSource(const char *file, DropType image); - void AddDropURL(const char *url, QString &name, obs_data_t *settings, const obs_video_info &ovi); - void ConfirmDropUrl(const QString &url); - void dragEnterEvent(QDragEnterEvent *event) override; - void dragLeaveEvent(QDragLeaveEvent *event) override; - void dragMoveEvent(QDragMoveEvent *event) override; - void dropEvent(QDropEvent *event) override; - - bool sysTrayMinimizeToTray(); - - void EnumDialogs(); - - QList visDialogs; - QList modalDialogs; - QList visMsgBoxes; - - QList visDlgPositions; - - QByteArray startingDockLayout; - - obs_data_array_t *SaveProjectors(); - void LoadSavedProjectors(obs_data_array_t *savedProjectors); - - void MacBranchesFetched(const QString &branch, bool manualUpdate); - void ReceivedIntroJson(const QString &text); - void ShowWhatsNew(const QString &url); - - void UpdatePreviewProgramIndicators(); - - QStringList extraDockNames; - QList> extraDocks; - - QStringList extraCustomDockNames; - QList> extraCustomDocks; + /* ------------------------------------- + * MARK: - OBSBasic_Browser + * ------------------------------------- + */ +private: + QPointer extraBrowsers; #ifdef BROWSER_AVAILABLE QPointer extraBrowserMenuDocksSeparator; @@ -579,6 +361,141 @@ private: void AddExtraBrowserDock(const QString &title, const QString &url, const QString &uuid, bool firstCreate); #endif +public: + static void InitBrowserPanelSafeBlock(); + + /* ------------------------------------- + * MARK: - OBSBasic_Clipboard + * ------------------------------------- + */ +private: + std::deque clipboard; + OBSWeakSourceAutoRelease copyFiltersSource; + obs_transform_info copiedTransformInfo; + obs_sceneitem_crop copiedCropInfo; + bool hasCopiedTransform = false; + int copySourceTransitionDuration; + OBSWeakSourceAutoRelease copySourceTransition; + +private slots: + void on_actionCopySource_triggered(); + void on_actionPasteRef_triggered(); + void on_actionPasteDup_triggered(); + + void on_actionCopyFilters_triggered(); + void on_actionPasteFilters_triggered(); + void AudioMixerCopyFilters(); + void AudioMixerPasteFilters(); + void SourcePasteFilters(OBSSource source, OBSSource dstSource); + + void SceneCopyFilters(); + void ScenePasteFilters(); + + void on_actionCopyTransform_triggered(); + void on_actionPasteTransform_triggered(); + +public: + OBSWeakSource copyFilter; + void CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, + obs_data_array_t *redo_array); + + /* ------------------------------------- + * MARK: - OBSBasic_ContextToolbar + * ------------------------------------- + */ +private: + ContextBarSize contextBarSize = ContextBarSize_Normal; + + void SourceToolBarActionsSetEnabled(); + + void copyActionsDynamicProperties(); + +private slots: + void on_toggleContextBar_toggled(bool visible); + +public slots: + void ShowContextBar(); + void HideContextBar(); + + void ClearContextBar(); + void UpdateContextBar(bool force = false); + void UpdateContextBarDeferred(bool force = false); + void UpdateContextBarVisibility(); + + /* ------------------------------------- + * MARK: - OBSBasic_Docks + * ------------------------------------- + */ +private: + QList> oldExtraDocks; + QStringList oldExtraDockNames; + QPointer statsDock; + QByteArray startingDockLayout; + QStringList extraDockNames; + QList> extraDocks; + + QStringList extraCustomDockNames; + QList> extraCustomDocks; + + QPointer controlsDock; + +public: + QAction *AddDockWidget(QDockWidget *dock); + void AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser = false); + void RemoveDockWidget(const QString &name); + bool IsDockObjectNameUsed(const QString &name); + void AddCustomDockWidget(QDockWidget *dock); + +private slots: + void on_resetDocks_triggered(bool force = false); + void on_lockDocks_toggled(bool lock); + void on_sideDocks_toggled(bool side); + + void RepairOldExtraDockName(); + void RepairCustomExtraDockName(); + + /* ------------------------------------- + * MARK: - OBSBasic_Dropfiles + * ------------------------------------- + */ +private: + void AddDropSource(const char *file, DropType image); + void AddDropURL(const char *url, QString &name, obs_data_t *settings, const obs_video_info &ovi); + void ConfirmDropUrl(const QString &url); + void dragEnterEvent(QDragEnterEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + + /* ------------------------------------- + * MARK: - OBSBasic_Hotkeys + * ------------------------------------- + */ +private: + QPointer shortcutFilter; + obs_hotkey_id statsHotkey = 0; + obs_hotkey_id screenshotHotkey = 0; + obs_hotkey_id sourceScreenshotHotkey = 0; + + obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys, replayBufHotkeys, vcamHotkeys, + togglePreviewHotkeys, contextBarHotkeys; + obs_hotkey_id forceStreamingStopHotkey, splitFileHotkey, addChapterHotkey; + + void InitHotkeys(); + void CreateHotkeys(); + void ClearHotkeys(); + + static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); + +private slots: + void ProcessHotkey(obs_hotkey_id id, bool pressed); + void ResetStatsHotkey(); + + /* ------------------------------------- + * MARK: - OBSBasic_Icons + * ------------------------------------- + */ +private: QIcon imageIcon; QIcon colorIcon; QIcon slideshowIcon; @@ -611,216 +528,7 @@ private: QIcon GetDefaultIcon() const; QIcon GetAudioProcessOutputIcon() const; - QSlider *tBar; - bool tBarActive = false; - - OBSSource GetOverrideTransition(OBSSource source); - int GetOverrideTransitionDuration(OBSSource source); - - void UpdateProjectorHideCursor(); - void UpdateProjectorAlwaysOnTop(bool top); - void ResetProjectors(); - - QPointer screenshotData; - - void MoveSceneItem(enum obs_order_movement movement, const QString &action_name); - - bool autoStartBroadcast = true; - bool autoStopBroadcast = true; - bool broadcastActive = false; - bool broadcastReady = false; - QPointer youtubeStreamCheckThread; -#ifdef YOUTUBE_ENABLED - void YoutubeStreamCheck(const std::string &key); - void ShowYouTubeAutoStartWarning(); - void YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now); -#endif - void BroadcastButtonClicked(); - void SetBroadcastFlowEnabled(bool enabled); - - void UpdatePreviewSafeAreas(); - bool drawSafeAreas = false; - - void CenterSelectedSceneItems(const CenterType ¢erType); - void ShowMissingFilesDialog(obs_missing_files_t *files); - - QColor selectionColor; - QColor cropColor; - QColor hoverColor; - - QColor GetCropColor() const; - QColor GetHoverColor() const; - - void UpdatePreviewSpacingHelpers(); - bool drawSpacingHelpers = true; - - float GetDevicePixelRatio(); - void SourceToolBarActionsSetEnabled(); - - std::string lastScreenshot; - std::string lastReplay; - - void UpdatePreviewOverflowSettings(); - void UpdatePreviewScrollbars(); - - bool streamingStarting = false; - - bool recordingStarted = false; - bool isRecordingPausable = false; - bool recordingPaused = false; - - bool restartingVCam = false; - -public slots: - void DeferSaveBegin(); - void DeferSaveEnd(); - - void DisplayStreamStartError(); - - void SetupBroadcast(); - - void StartStreaming(); - void StopStreaming(); - void ForceStopStreaming(); - - void StreamDelayStarting(int sec); - void StreamDelayStopping(int sec); - - void StreamingStart(); - void StreamStopping(); - void StreamingStop(int errorcode, QString last_error); - - void StartRecording(); - void StopRecording(); - - void RecordingStart(); - void RecordStopping(); - void RecordingStop(int code, QString last_error); - void RecordingFileChanged(QString lastRecordingPath); - - void ShowReplayBufferPauseWarning(); - void StartReplayBuffer(); - void StopReplayBuffer(); - - void ReplayBufferStart(); - void ReplayBufferSave(); - void ReplayBufferSaved(); - void ReplayBufferStopping(); - void ReplayBufferStop(int code); - - void StartVirtualCam(); - void StopVirtualCam(); - - void OnVirtualCamStart(); - void OnVirtualCamStop(int code); - - void SaveProjectDeferred(); - void SaveProject(); - - void SetTransition(OBSSource transition); - void OverrideTransition(OBSSource transition); - void TransitionToScene(OBSScene scene, bool force = false); - void TransitionToScene(OBSSource scene, bool force = false, bool quickTransition = false, int quickDuration = 0, - bool black = false, bool manual = false); - void SetCurrentScene(OBSSource scene, bool force = false); - - void UpdatePatronJson(const QString &text, const QString &error); - - void ShowContextBar(); - void HideContextBar(); - void PauseRecording(); - void UnpauseRecording(); - - void UpdateEditMenu(); - private slots: - - void on_actionMainUndo_triggered(); - void on_actionMainRedo_triggered(); - - void AddSceneItem(OBSSceneItem item); - void AddScene(OBSSource source); - void RemoveScene(OBSSource source); - void RenameSources(OBSSource source, QString newName, QString prevName); - - void ActivateAudioSource(OBSSource source); - void DeactivateAudioSource(OBSSource source); - - void DuplicateSelectedScene(); - void RemoveSelectedScene(); - - void ToggleAlwaysOnTop(); - - void ReorderSources(OBSScene scene); - void RefreshSources(OBSScene scene); - - void ProcessHotkey(obs_hotkey_id id, bool pressed); - - void AddTransition(const char *id); - void RenameTransition(OBSSource transition); - void TransitionClicked(); - void TransitionStopped(); - void TransitionFullyStopped(); - void TriggerQuickTransition(int id); - - void SetDeinterlacingMode(); - void SetDeinterlacingOrder(); - - void SetScaleFilter(); - - void SetBlendingMethod(); - void SetBlendingMode(); - - void IconActivated(QSystemTrayIcon::ActivationReason reason); - void SetShowing(bool showing); - - void ToggleShowHide(); - - void HideAudioControl(); - void UnhideAllAudioControls(); - void ToggleHideMixer(); - - void MixerRenameSource(); - - void on_vMixerScrollArea_customContextMenuRequested(); - void on_hMixerScrollArea_customContextMenuRequested(); - - void on_actionCopySource_triggered(); - void on_actionPasteRef_triggered(); - void on_actionPasteDup_triggered(); - - void on_actionCopyFilters_triggered(); - void on_actionPasteFilters_triggered(); - void AudioMixerCopyFilters(); - void AudioMixerPasteFilters(); - void SourcePasteFilters(OBSSource source, OBSSource dstSource); - - void on_previewXScrollBar_valueChanged(int value); - void on_previewYScrollBar_valueChanged(int value); - - void PreviewScalingModeChanged(int value); - - void ColorChange(); - - SourceTreeItem *GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem); - - void on_actionShowAbout_triggered(); - - void EnablePreview(); - void DisablePreview(); - - void EnablePreviewProgram(); - void DisablePreviewProgram(); - - void SceneCopyFilters(); - void ScenePasteFilters(); - - void CheckDiskSpaceRemaining(); - void OpenSavedProjector(SavedProjectorInfo *info); - - void ResetStatsHotkey(); - void SetImageIcon(const QIcon &icon); void SetColorIcon(const QIcon &icon); void SetSlideshowIcon(const QIcon &icon); @@ -838,202 +546,64 @@ private slots: void SetDefaultIcon(const QIcon &icon); void SetAudioProcessOutputIcon(const QIcon &icon); - void TBarChanged(int value); - void TBarReleased(); - - void LockVolumeControl(bool lock); - - void UpdateVirtualCamConfig(const VCamConfig &config); - void RestartVirtualCam(const VCamConfig &config); - void RestartingVirtualCam(); - -private: - /* OBS Callbacks */ - static void SceneReordered(void *data, calldata_t *params); - static void SceneRefreshed(void *data, calldata_t *params); - static void SceneItemAdded(void *data, calldata_t *params); - static void SourceCreated(void *data, calldata_t *params); - static void SourceRemoved(void *data, calldata_t *params); - static void SourceActivated(void *data, calldata_t *params); - static void SourceDeactivated(void *data, calldata_t *params); - static void SourceAudioActivated(void *data, calldata_t *params); - static void SourceAudioDeactivated(void *data, calldata_t *params); - static void SourceRenamed(void *data, calldata_t *params); - static void RenderMain(void *data, uint32_t cx, uint32_t cy); - - void ResizePreview(uint32_t cx, uint32_t cy); - - void AddSource(const char *id); - QMenu *CreateAddSourcePopupMenu(); - void AddSourcePopupMenu(const QPoint &pos); - void copyActionsDynamicProperties(); - - static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); - - void AutoRemux(QString input, bool no_show = false); - - void UpdateIsRecordingPausable(); - - bool IsFFmpegOutputToURL() const; - bool OutputPathValid(); - void OutputPathInvalidMessage(); - - bool LowDiskSpace(); - void DiskSpaceMessage(); - - OBSSource prevFTBSource = nullptr; - - float dpi = 1.0; - public: - OBSSource GetProgramSource(); - OBSScene GetCurrentScene(); - - void SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n); - - inline OBSSource GetCurrentSceneSource() - { - OBSScene curScene = GetCurrentScene(); - return OBSSource(obs_scene_get_source(curScene)); - } - - obs_service_t *GetService(); - void SetService(obs_service_t *service); - - int GetTransitionDuration(); - int GetTbarPosition(); - - inline bool IsPreviewProgramMode() const { return os_atomic_load_bool(&previewProgramMode); } - - inline bool VCamEnabled() const { return vcamEnabled; } - - bool Active() const; - - void ResetUI(); - int ResetVideo(); - bool ResetAudio(); - - void ResetOutputs(); - - void RefreshVolumeColors(); - - void ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel); - - void NewProject(); - void LoadProject(); - - inline void GetDisplayRect(int &x, int &y, int &cx, int &cy) - { - x = previewX; - y = previewY; - cx = previewCX; - cy = previewCY; - } - - inline bool SavingDisabled() const { return disableSaving; } - - inline double GetCPUUsage() const { return os_cpu_usage_info_query(cpuUsageInfo); } - - void SaveService(); - bool LoadService(); - - inline Auth *GetAuth() { return auth.get(); } - - inline void EnableOutputs(bool enable) - { - if (enable) { - if (--disableOutputsRef < 0) - disableOutputsRef = 0; - } else { - disableOutputsRef++; - } - } - - QMenu *AddDeinterlacingMenu(QMenu *menu, obs_source_t *source); - QMenu *AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item); - QMenu *AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item); - void CreateSourcePopupMenu(int idx, bool preview); - - void UpdateTitleBar(); - - void SystemTrayInit(); - void SystemTray(bool firstStarted); - - void OpenSavedProjectors(); - - void CreateInteractionWindow(obs_source_t *source); - void CreatePropertiesWindow(obs_source_t *source); - void CreateFiltersWindow(obs_source_t *source); - void CreateEditTransformWindow(obs_sceneitem_t *item); - - QAction *AddDockWidget(QDockWidget *dock); - void AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser = false); - void RemoveDockWidget(const QString &name); - bool IsDockObjectNameUsed(const QString &name); - void AddCustomDockWidget(QDockWidget *dock); - - static OBSBasic *Get(); - - const char *GetCurrentOutputPath(); - - void DeleteProjector(OBSProjector *projector); - - static QList GetProjectorMenuMonitorsFormatted(); - template - static void AddProjectorMenuMonitors(QMenu *parent, Receiver *target, void (Receiver::*slot)(Args...)) - { - auto projectors = GetProjectorMenuMonitorsFormatted(); - for (int i = 0; i < projectors.size(); i++) { - QString str = projectors[i]; - QAction *action = parent->addAction(str, target, slot); - action->setProperty("monitor", i); - } - } - QIcon GetSourceIcon(const char *id) const; QIcon GetGroupIcon() const; QIcon GetSceneIcon() const; - OBSWeakSource copyFilter; + /* ------------------------------------- + * MARK: - OBSBasic_MainControls + * ------------------------------------- + */ +private: + QPointer interaction; + QPointer properties; + QPointer transformWindow; + QPointer advAudioWindow; + QPointer filters; + QPointer about; + QPointer logView; + QPointer stats; + QPointer remux; + QPointer importer; + QPointer showHide; + QPointer exit; - void ShowStatusBarMessage(const QString &message); + QPointer scaleFilteringMenu; + QPointer blendingMethodMenu; + QPointer blendingModeMenu; + QPointer colorMenu; + QPointer deinterlaceMenu; - static OBSData BackupScene(obs_scene_t *scene, std::vector *sources = nullptr); - void CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data); + QPointer colorWidgetAction; + QPointer colorSelect; - static inline OBSData BackupScene(obs_source_t *scene_source, std::vector *sources = nullptr) - { - obs_scene_t *scene = obs_scene_from_source(scene_source); - return BackupScene(scene, sources); - } + QList visDialogs; + QList modalDialogs; + QList visMsgBoxes; - void CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array); + QList visDlgPositions; - void SetDisplayAffinity(QWindow *window); - - QColor GetSelectionColor() const; - inline bool Closing() { return closing; } - -protected: - virtual void closeEvent(QCloseEvent *event) override; - virtual bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override; - virtual void changeEvent(QEvent *event) override; + void UploadLog(const char *subdir, const char *file, const bool crash); + void CloseDialogs(); + void EnumDialogs(); private slots: - void on_actionFullscreenInterface_triggered(); + void on_actionMainUndo_triggered(); + void on_actionMainRedo_triggered(); + void ToggleAlwaysOnTop(); - void on_actionShow_Recordings_triggered(); + void SetShowing(bool showing); + + void ToggleShowHide(); + + void on_actionShowAbout_triggered(); + + void on_actionFullscreenInterface_triggered(); void on_actionRemux_triggered(); void on_action_Settings_triggered(); void on_actionShowMacPermissions_triggered(); - void on_actionShowMissingFiles_triggered(); void on_actionAdvAudioProperties_triggered(); - void on_actionMixerToolbarAdvAudio_triggered(); - void on_actionMixerToolbarMenu_triggered(); void on_actionShowLogs_triggered(); void on_actionUploadCurrentLog_triggered(); void on_actionUploadLastLog_triggered(); @@ -1046,45 +616,220 @@ private slots: void on_actionShowCrashLogs_triggered(); void on_actionUploadLastCrashLog_triggered(); - void on_actionEditTransform_triggered(); - void on_actionCopyTransform_triggered(); - void on_actionPasteTransform_triggered(); - void on_actionRotate90CW_triggered(); - void on_actionRotate90CCW_triggered(); - void on_actionRotate180_triggered(); - void on_actionFlipHorizontal_triggered(); - void on_actionFlipVertical_triggered(); - void on_actionFitToScreen_triggered(); - void on_actionStretchToScreen_triggered(); - void on_actionCenterToScreen_triggered(); - void on_actionVerticalCenter_triggered(); - void on_actionHorizontalCenter_triggered(); - void on_actionSceneFilters_triggered(); - void on_OBSBasic_customContextMenuRequested(const QPoint &pos); - void on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *prev); - void on_scenes_customContextMenuRequested(const QPoint &pos); - void GridActionClicked(); - void on_actionSceneListMode_triggered(); - void on_actionSceneGridMode_triggered(); - void on_actionAddScene_triggered(); - void on_actionRemoveScene_triggered(); - void on_actionSceneUp_triggered(); - void on_actionSceneDown_triggered(); - void on_sources_customContextMenuRequested(const QPoint &pos); - void on_scenes_itemDoubleClicked(QListWidgetItem *item); - void on_actionAddSource_triggered(); - void on_actionRemoveSource_triggered(); - void on_actionInteract_triggered(); - void on_actionSourceProperties_triggered(); - void on_actionSourceUp_triggered(); - void on_actionSourceDown_triggered(); + void on_actionHelpPortal_triggered(); + void on_actionWebsite_triggered(); + void on_actionDiscord_triggered(); + void on_actionReleaseNotes_triggered(); - void on_actionMoveUp_triggered(); - void on_actionMoveDown_triggered(); - void on_actionMoveToTop_triggered(); - void on_actionMoveToBottom_triggered(); + void on_actionShowSettingsFolder_triggered(); + void on_actionShowProfileFolder_triggered(); + + void on_actionAlwaysOnTop_triggered(); + + void on_toggleListboxToolbars_toggled(bool visible); + void on_toggleStatusBar_toggled(bool visible); + + void on_autoConfigure_triggered(); + void on_stats_triggered(); + + void on_resetUI_triggered(); + + void logUploadFinished(const QString &text, const QString &error); + void crashUploadFinished(const QString &text, const QString &error); + void openLogDialog(const QString &text, const bool crash); + + void updateCheckFinished(); + +public: + void ResetUI(); + + void CreateInteractionWindow(obs_source_t *source); + void CreateFiltersWindow(obs_source_t *source); + void CreateEditTransformWindow(obs_sceneitem_t *item); + void CreatePropertiesWindow(obs_source_t *source); + + /* ------------------------------------- + * MARK: - OBSBasic_OutputHandler + * ------------------------------------- + */ +private: + std::unique_ptr outputHandler; + std::optional> lastOutputResolution; + + int disableOutputsRef = 0; + + inline void OnActivate(bool force = false) + { + if (ui->profileMenu->isEnabled() || force) { + ui->profileMenu->setEnabled(false); + ui->autoConfigure->setEnabled(false); + App()->IncrementSleepInhibition(); + UpdateProcessPriority(); + + struct obs_video_info ovi; + obs_get_video_info(&ovi); + lastOutputResolution = {ovi.base_width, ovi.base_height}; + + TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); + if (trayIcon && trayIcon->isVisible()) { +#ifdef __APPLE__ + QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); + trayMask.setIsMask(true); + trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); +#else + trayIcon->setIcon( + QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); +#endif + } + } + } + + inline void OnDeactivate() + { + if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { + ui->profileMenu->setEnabled(true); + ui->autoConfigure->setEnabled(true); + App()->DecrementSleepInhibition(); + ClearProcessPriority(); + + TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); + if (trayIcon && trayIcon->isVisible()) { +#ifdef __APPLE__ + QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); + trayIconFile.setIsMask(true); +#else + QIcon trayIconFile = QIcon(":/res/images/obs.png"); +#endif + trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); + } + } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { + if (os_atomic_load_bool(&recording_paused)) { +#ifdef __APPLE__ + QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); + trayIconFile.setIsMask(true); +#else + QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); +#endif + trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); + TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); + } else { +#ifdef __APPLE__ + QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); + trayIconFile.setIsMask(true); +#else + QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); +#endif + trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); + TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); + } + } + } + + bool IsFFmpegOutputToURL() const; + bool OutputPathValid(); + void OutputPathInvalidMessage(); + + // TODO: Unimplemented, remove. + void SetupEncoders(); + // TODO: Unimplemented, remove. + void TempFileOutput(const char *path, int vBitrate, int aBitrate); + // TODO: Unimplemented, remove. + void TempStreamOutput(const char *url, const char *key, int vBitrate, int aBitrate); + +public: + bool Active() const; + void ResetOutputs(); + + inline void EnableOutputs(bool enable) + { + if (enable) { + if (--disableOutputsRef < 0) + disableOutputsRef = 0; + } else { + disableOutputsRef++; + } + } + + const char *GetCurrentOutputPath(); + +private slots: + void ResizeOutputSizeOfSource(); + + /* ------------------------------------- + * MARK: - OBSBasic_Preview + * ------------------------------------- + */ +private: + bool previewEnabled = true; + QPointer nudge_timer; + bool recent_nudge = false; + + gs_vertbuffer_t *box = nullptr; + gs_vertbuffer_t *boxLeft = nullptr; + gs_vertbuffer_t *boxTop = nullptr; + gs_vertbuffer_t *boxRight = nullptr; + gs_vertbuffer_t *boxBottom = nullptr; + gs_vertbuffer_t *circle = nullptr; + + gs_vertbuffer_t *actionSafeMargin = nullptr; + gs_vertbuffer_t *graphicsSafeMargin = nullptr; + gs_vertbuffer_t *fourByThreeSafeMargin = nullptr; + gs_vertbuffer_t *leftLine = nullptr; + gs_vertbuffer_t *topLine = nullptr; + gs_vertbuffer_t *rightLine = nullptr; + + int previewX = 0, previewY = 0; + int previewCX = 0, previewCY = 0; + float previewScale = 0.0f; + bool drawSafeAreas = false; + + QColor selectionColor; + QColor cropColor; + QColor hoverColor; + + bool drawSpacingHelpers = true; + + float dpi = 1.0; + + void DrawBackdrop(float cx, float cy); + void InitPrimitives(); + void UpdatePreviewScalingMenu(); + + void Nudge(int dist, MoveDir dir); + + void UpdateProjectorHideCursor(); + void UpdateProjectorAlwaysOnTop(bool top); + void ResetProjectors(); + + void UpdatePreviewSafeAreas(); + + QColor GetCropColor() const; + QColor GetHoverColor() const; + + void UpdatePreviewSpacingHelpers(); + + float GetDevicePixelRatio(); + + void UpdatePreviewOverflowSettings(); + void UpdatePreviewScrollbars(); + + /* OBS Callbacks */ + static void RenderMain(void *data, uint32_t cx, uint32_t cy); + + void ResizePreview(uint32_t cx, uint32_t cy); + +private slots: + void on_previewXScrollBar_valueChanged(int value); + void on_previewYScrollBar_valueChanged(int value); + + void PreviewScalingModeChanged(int value); + + void ColorChange(); + + void EnablePreview(); + void DisablePreview(); void on_actionLockPreview_triggered(); @@ -1093,161 +838,24 @@ private slots: void on_actionScaleCanvas_triggered(); void on_actionScaleOutput_triggered(); - void Screenshot(OBSSource source_ = nullptr); - void ScreenshotSelectedSource(); - void ScreenshotProgram(); - void ScreenshotScene(); - - void on_actionHelpPortal_triggered(); - void on_actionWebsite_triggered(); - void on_actionDiscord_triggered(); - void on_actionReleaseNotes_triggered(); - void on_preview_customContextMenuRequested(); - void ProgramViewContextMenuRequested(); void on_previewDisabledWidget_customContextMenuRequested(); - void on_actionShowSettingsFolder_triggered(); - void on_actionShowProfileFolder_triggered(); - - void on_actionAlwaysOnTop_triggered(); - - void on_toggleListboxToolbars_toggled(bool visible); - void on_toggleContextBar_toggled(bool visible); - void on_toggleStatusBar_toggled(bool visible); - void on_toggleSourceIcons_toggled(bool visible); - - void on_transitions_currentIndexChanged(int index); - void on_transitionAdd_clicked(); - void on_transitionRemove_clicked(); - void on_transitionProps_clicked(); - void on_transitionDuration_valueChanged(); - - void ShowTransitionProperties(); - void HideTransitionProperties(); - - // Source Context Buttons - void on_sourcePropertiesButton_clicked(); - void on_sourceFiltersButton_clicked(); - void on_sourceInteractButton_clicked(); - - void on_autoConfigure_triggered(); - void on_stats_triggered(); - - void on_resetUI_triggered(); - void on_resetDocks_triggered(bool force = false); - void on_lockDocks_toggled(bool lock); - void on_multiviewProjectorWindowed_triggered(); - void on_sideDocks_toggled(bool side); - - void logUploadFinished(const QString &text, const QString &error); - void crashUploadFinished(const QString &text, const QString &error); - void openLogDialog(const QString &text, const bool crash); - - void updateCheckFinished(); - - void MoveSceneToTop(); - void MoveSceneToBottom(); - - void EditSceneName(); - void EditSceneItemName(); - - void SceneNameEdited(QWidget *editor); - - void OpenSceneFilters(); - void OpenFilters(OBSSource source = nullptr); - void OpenProperties(OBSSource source = nullptr); - void OpenInteraction(OBSSource source = nullptr); - void OpenEditTransform(OBSSceneItem item = nullptr); - void EnablePreviewDisplay(bool enable); void TogglePreview(); - void OpenStudioProgramProjector(); - void OpenPreviewProjector(); - void OpenSourceProjector(); - void OpenMultiviewProjector(); - void OpenSceneProjector(); +public: + inline void GetDisplayRect(int &x, int &y, int &cx, int &cy) + { + x = previewX; + y = previewY; + cx = previewCX; + cy = previewCY; + } - void OpenStudioProgramWindow(); - void OpenPreviewWindow(); - void OpenSourceWindow(); - void OpenSceneWindow(); - - void StackedMixerAreaContextMenuRequested(); - - void ResizeOutputSizeOfSource(); - - void RepairOldExtraDockName(); - void RepairCustomExtraDockName(); - - /* Stream action (start/stop) slot */ - void StreamActionTriggered(); - - /* Record action (start/stop) slot */ - void RecordActionTriggered(); - - /* Record pause (pause/unpause) slot */ - void RecordPauseToggled(); - - /* Replay Buffer action (start/stop) slot */ - void ReplayBufferActionTriggered(); - - /* Virtual Cam action (start/stop) slots */ - void VirtualCamActionTriggered(); - - void OpenVirtualCamConfig(); - - /* Studio Mode toggle slot */ - void TogglePreviewProgramMode(); - -public slots: - void on_actionResetTransform_triggered(); - - bool StreamingActive(); - bool RecordingActive(); - bool ReplayBufferActive(); - bool VirtualCamActive(); - - void ClearContextBar(); - void UpdateContextBar(bool force = false); - void UpdateContextBarDeferred(bool force = false); - void UpdateContextBarVisibility(); + QColor GetSelectionColor() const; signals: - /* Streaming signals */ - void StreamingPreparing(); - void StreamingStarting(bool broadcastAutoStart); - void StreamingStarted(bool withDelay = false); - void StreamingStopping(); - void StreamingStopped(bool withDelay = false); - - /* Broadcast Flow signals */ - void BroadcastFlowEnabled(bool enabled); - void BroadcastStreamReady(bool ready); - void BroadcastStreamActive(); - void BroadcastStreamStarted(bool autoStop); - - /* Recording signals */ - void RecordingStarted(bool pausable = false); - void RecordingPaused(); - void RecordingUnpaused(); - void RecordingStopping(); - void RecordingStopped(); - - /* Replay Buffer signals */ - void ReplayBufEnabled(bool enabled); - void ReplayBufStarted(); - void ReplayBufStopping(); - void ReplayBufStopped(); - - /* Virtual Camera signals */ - void VirtualCamEnabled(); - void VirtualCamStarted(); - void VirtualCamStopped(); - - /* Studio Mode signal */ - void PreviewProgramModeChanged(bool enabled); void CanvasResized(uint32_t width, uint32_t height); void OutputResized(uint32_t width, uint32_t height); @@ -1255,35 +863,10 @@ signals: void PreviewXScrollBarMoved(int value); void PreviewYScrollBarMoved(int value); -private: - std::unique_ptr ui; - - QPointer controlsDock; - -public: - /* `undo_s` needs to be declared after `ui` to prevent an uninitialized - * warning for `ui` while initializing `undo_s`. */ - undo_stack undo_s; - - explicit OBSBasic(QWidget *parent = 0); - virtual ~OBSBasic(); - - virtual void OBSInit() override; - - virtual config_t *Config() const override; - - virtual int GetProfilePath(char *path, size_t size, const char *file) const override; - - static void InitBrowserPanelSafeBlock(); -#ifdef YOUTUBE_ENABLED - void NewYouTubeAppDock(); - void DeleteYouTubeAppDock(); - YouTubeAppDock *GetYouTubeAppDock(); -#endif - // MARK: - Generic UI Helper Functions - OBSPromptResult PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback); - - // MARK: - OBS Profile Management + /* ------------------------------------- + * MARK: - OBSBasic_Profiles + * ------------------------------------- + */ private: OBSProfileCache profiles{}; @@ -1327,10 +910,162 @@ public slots: bool CreateDuplicateProfile(const QString &name); void DeleteProfile(const QString &profileName); - // MARK: - OBS Scene Collection Management + /* ------------------------------------- + * MARK: - OBSBasic_Projectors + * ------------------------------------- + */ private: + std::vector savedProjectorsArray; + std::vector projectors; + QPointer previewProjector; + QPointer previewProjectorSource; + QPointer previewProjectorMain; + + void UpdateMultiviewProjectorMenu(); + void ClearProjectors(); + OBSProjector *OpenProjector(obs_source_t *source, int monitor, ProjectorType type); + + obs_data_array_t *SaveProjectors(); + void LoadSavedProjectors(obs_data_array_t *savedProjectors); + +private slots: + void OpenSavedProjector(SavedProjectorInfo *info); + void on_multiviewProjectorWindowed_triggered(); + + void OpenPreviewProjector(); + void OpenSourceProjector(); + void OpenMultiviewProjector(); + void OpenSceneProjector(); + + void OpenPreviewWindow(); + void OpenSourceWindow(); + void OpenSceneWindow(); + +public: + void OpenSavedProjectors(); + void DeleteProjector(OBSProjector *projector); + + static QList GetProjectorMenuMonitorsFormatted(); + template + static void AddProjectorMenuMonitors(QMenu *parent, Receiver *target, void (Receiver::*slot)(Args...)) + { + auto projectors = GetProjectorMenuMonitorsFormatted(); + for (int i = 0; i < projectors.size(); i++) { + QString str = projectors[i]; + QAction *action = parent->addAction(str, target, slot); + action->setProperty("monitor", i); + } + } + + /* ------------------------------------- + * MARK: - OBSBasic_Recording + * ------------------------------------- + */ +private: + QPointer diskFullTimer; + bool recordingStopping = false; + bool recordingStarted = false; + bool isRecordingPausable = false; + bool recordingPaused = false; + + void AutoRemux(QString input, bool no_show = false); + void UpdateIsRecordingPausable(); + + bool LowDiskSpace(); + void DiskSpaceMessage(); + +private slots: + void on_actionShow_Recordings_triggered(); + + /* Record action (start/stop) slot */ + void RecordActionTriggered(); + + /* Record pause (pause/unpause) slot */ + void RecordPauseToggled(); + +public slots: + void StartRecording(); + void StopRecording(); + + void RecordingStart(); + void RecordStopping(); + void RecordingStop(int code, QString last_error); + void RecordingFileChanged(QString lastRecordingPath); + + void PauseRecording(); + void UnpauseRecording(); + + void CheckDiskSpaceRemaining(); + + bool RecordingActive(); + +signals: + /* Recording signals */ + void RecordingStarted(bool pausable = false); + void RecordingPaused(); + void RecordingUnpaused(); + void RecordingStopping(); + void RecordingStopped(); + + /* ------------------------------------- + * MARK: - OBSBasic_ReplayBuffer + * ------------------------------------- + */ +private: + bool replayBufferStopping = false; + std::string lastReplay; + +public slots: + void ShowReplayBufferPauseWarning(); + void StartReplayBuffer(); + void StopReplayBuffer(); + + void ReplayBufferStart(); + void ReplayBufferSave(); + void ReplayBufferSaved(); + void ReplayBufferStopping(); + void ReplayBufferStop(int code); + + bool ReplayBufferActive(); + +private slots: + /* Replay Buffer action (start/stop) slot */ + void ReplayBufferActionTriggered(); + +signals: + /* Replay Buffer signals */ + void ReplayBufEnabled(bool enabled); + void ReplayBufStarted(); + void ReplayBufStopping(); + void ReplayBufStopped(); + + /* ------------------------------------- + * MARK: - OBSBasic_SceneCollections + * ------------------------------------- + */ +private: + OBSDataAutoRelease collectionModuleData; + long disableSaving = 1; + bool projectChanged = false; + bool clearingFailed = false; + + QPointer missDialog; + std::optional> migrationBaseResolution; + bool usingAbsoluteCoordinates = false; + OBSSceneCollectionCache collections{}; + void DisableRelativeCoordinates(bool disable); + void CreateDefaultScene(bool firstStart); + void Save(const char *file); + void LoadData(obs_data_t *data, const char *file, bool remigrate = false); + void Load(const char *file, bool remigrate = false); + + void ClearSceneData(); + void LogScenes(); + void SaveProjectNow(); + void ShowMissingFilesDialog(obs_missing_files_t *files); + void SetupNewSceneCollection(const std::string &collectionName); void SetupDuplicateSceneCollection(const std::string &collectionName); void SetupRenameSceneCollection(const std::string &collectionName); @@ -1347,15 +1082,18 @@ private: void RefreshSceneCollections(bool refreshCache = false); void ActivateSceneCollection(const OBSSceneCollection &collection); -public: - inline const OBSSceneCollectionCache &GetSceneCollectionCache() const noexcept { return collections; }; +public slots: + void DeferSaveBegin(); + void DeferSaveEnd(); - const OBSSceneCollection &GetCurrentSceneCollection() const; + void SaveProjectDeferred(); + void SaveProject(); - std::optional GetSceneCollectionByName(const std::string &collectionName) const; - std::optional GetSceneCollectionByFileName(const std::string &fileName) const; + bool CreateNewSceneCollection(const QString &name); private slots: + void on_actionShowMissingFiles_triggered(); + void on_actionNewSceneCollection_triggered(); void on_actionDupSceneCollection_triggered(); void on_actionRenameSceneCollection_triggered(); @@ -1364,19 +1102,573 @@ private slots: void on_actionExportSceneCollection_triggered(); void on_actionRemigrateSceneCollection_triggered(); -public slots: - bool CreateNewSceneCollection(const QString &name); -}; +public: + inline bool SavingDisabled() const { return disableSaving; } -extern bool cef_js_avail; + inline const OBSSceneCollectionCache &GetSceneCollectionCache() const noexcept { return collections; }; -class SceneRenameDelegate : public QStyledItemDelegate { - Q_OBJECT + const OBSSceneCollection &GetCurrentSceneCollection() const; + + std::optional GetSceneCollectionByName(const std::string &collectionName) const; + std::optional GetSceneCollectionByFileName(const std::string &fileName) const; + + /* ------------------------------------- + * MARK: - OBSBasic_SceneItems + * ------------------------------------- + */ +private: + QPointer sourceProjector; + QPointer renameSource; + + void CreateFirstRunSources(); + + OBSSceneItem GetSceneItem(QListWidgetItem *item); + OBSSceneItem GetCurrentSceneItem(); + + bool QueryRemoveSource(obs_source_t *source); + int GetTopSelectedSourceItem(); + + void GetAudioSourceFilters(); + void GetAudioSourceProperties(); + + QModelIndexList GetAllSelectedSourceItems(); + + // TODO: Move back to transitions + QMenu *CreateVisibilityTransitionMenu(bool visible); + void CenterSelectedSceneItems(const CenterType ¢erType); + + /* OBS Callbacks */ + static void SourceCreated(void *data, calldata_t *params); + static void SourceRemoved(void *data, calldata_t *params); + static void SourceActivated(void *data, calldata_t *params); + static void SourceDeactivated(void *data, calldata_t *params); + static void SourceAudioActivated(void *data, calldata_t *params); + static void SourceAudioDeactivated(void *data, calldata_t *params); + static void SourceRenamed(void *data, calldata_t *params); + + void AddSource(const char *id); + QMenu *CreateAddSourcePopupMenu(); + void AddSourcePopupMenu(const QPoint &pos); + +private slots: + void RenameSources(OBSSource source, QString newName, QString prevName); + + void ActivateAudioSource(OBSSource source); + void DeactivateAudioSource(OBSSource source); + + void ReorderSources(OBSScene scene); + void RefreshSources(OBSScene scene); + + void SetDeinterlacingMode(); + void SetDeinterlacingOrder(); + + void SetScaleFilter(); + + void SetBlendingMethod(); + void SetBlendingMode(); + + void MixerRenameSource(); + + void on_actionRotate90CW_triggered(); + void on_actionRotate90CCW_triggered(); + void on_actionRotate180_triggered(); + void on_actionFlipHorizontal_triggered(); + void on_actionFlipVertical_triggered(); + void on_actionFitToScreen_triggered(); + void on_actionStretchToScreen_triggered(); + void on_actionCenterToScreen_triggered(); + void on_actionVerticalCenter_triggered(); + void on_actionHorizontalCenter_triggered(); + + void on_actionEditTransform_triggered(); + + void on_sources_customContextMenuRequested(const QPoint &pos); + + // Source Context Buttons + void on_sourcePropertiesButton_clicked(); + void on_sourceFiltersButton_clicked(); + void on_sourceInteractButton_clicked(); + + void on_actionAddSource_triggered(); + void on_actionRemoveSource_triggered(); + void on_actionInteract_triggered(); + void on_actionSourceProperties_triggered(); + void on_actionSourceUp_triggered(); + void on_actionSourceDown_triggered(); + + void on_actionMoveUp_triggered(); + void on_actionMoveDown_triggered(); + void on_actionMoveToTop_triggered(); + void on_actionMoveToBottom_triggered(); + + void on_toggleSourceIcons_toggled(bool visible); + + void OpenFilters(OBSSource source = nullptr); + void OpenProperties(OBSSource source = nullptr); + void OpenInteraction(OBSSource source = nullptr); + void OpenEditTransform(OBSSceneItem item = nullptr); public: - SceneRenameDelegate(QObject *parent); - virtual void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel); -protected: - virtual bool eventFilter(QObject *editor, QEvent *event) override; + QMenu *AddDeinterlacingMenu(QMenu *menu, obs_source_t *source); + QMenu *AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item); + QMenu *AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item); + QMenu *AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item); + QMenu *AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, + obs_sceneitem_t *item); + void CreateSourcePopupMenu(int idx, bool preview); + + /* ------------------------------------- + * MARK: - OBSBasic_Scenes + * ------------------------------------- + */ +private: + QPointer sceneProjectorMenu; + QPointer renameScene; + std::atomic currentScene = nullptr; + OBSWeakSource lastScene; + OBSWeakSource swapScene; + + void LoadSceneListOrder(obs_data_array_t *array); + obs_data_array_t *SaveSceneListOrder(); + void ChangeSceneIndex(bool relative, int idx, int invalidIdx); + + void MoveSceneItem(enum obs_order_movement movement, const QString &action_name); + + /* OBS Callbacks */ + static void SceneReordered(void *data, calldata_t *params); + static void SceneRefreshed(void *data, calldata_t *params); + static void SceneItemAdded(void *data, calldata_t *params); + +public slots: + void on_actionResetTransform_triggered(); + +private slots: + void AddSceneItem(OBSSceneItem item); + void AddScene(OBSSource source); + void RemoveScene(OBSSource source); + + void DuplicateSelectedScene(); + void RemoveSelectedScene(); + + SourceTreeItem *GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem); + + void on_actionSceneFilters_triggered(); + + void on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *prev); + void on_scenes_customContextMenuRequested(const QPoint &pos); + + void GridActionClicked(); + void on_actionSceneListMode_triggered(); + void on_actionSceneGridMode_triggered(); + void on_actionAddScene_triggered(); + void on_actionRemoveScene_triggered(); + void on_actionSceneUp_triggered(); + void on_actionSceneDown_triggered(); + void on_scenes_itemDoubleClicked(QListWidgetItem *item); + + void MoveSceneToTop(); + void MoveSceneToBottom(); + + void EditSceneName(); + void EditSceneItemName(); + + void SceneNameEdited(QWidget *editor); + void OpenSceneFilters(); + +public: + static OBSData BackupScene(obs_scene_t *scene, std::vector *sources = nullptr); + static inline OBSData BackupScene(obs_source_t *scene_source, std::vector *sources = nullptr) + { + obs_scene_t *scene = obs_scene_from_source(scene_source); + return BackupScene(scene, sources); + } + + OBSScene GetCurrentScene(); + + inline OBSSource GetCurrentSceneSource() + { + OBSScene curScene = GetCurrentScene(); + return OBSSource(obs_scene_get_source(curScene)); + } + + void CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data); + + /* ------------------------------------- + * MARK: - OBSBasic_Screenshots + * ------------------------------------- + */ +private: + QPointer screenshotData; + std::string lastScreenshot; + +private slots: + void Screenshot(OBSSource source_ = nullptr); + void ScreenshotSelectedSource(); + void ScreenshotProgram(); + void ScreenshotScene(); + + /* ------------------------------------- + * MARK: - OBSBasic_Service + * ------------------------------------- + */ +private: + OBSService service; + + bool InitService(); + +public: + obs_service_t *GetService(); + void SetService(obs_service_t *service); + + void SaveService(); + bool LoadService(); + + /* ------------------------------------- + * MARK: - OBSBasic_StatusBar + * ------------------------------------- + */ +private: + QPointer cpuUsageTimer; + os_cpu_usage_info_t *cpuUsageInfo = nullptr; + +public: + inline double GetCPUUsage() const { return os_cpu_usage_info_query(cpuUsageInfo); } + void ShowStatusBarMessage(const QString &message); + + /* ------------------------------------- + * MARK: - OBSBasic_Streaming + * ------------------------------------- + */ +private: + std::shared_future setupStreamingGuard; + bool streamingStopping = false; + bool streamingStarting = false; + +public slots: + void DisplayStreamStartError(); + void StartStreaming(); + void StopStreaming(); + void ForceStopStreaming(); + + void StreamDelayStarting(int sec); + void StreamDelayStopping(int sec); + + void StreamingStart(); + void StreamStopping(); + void StreamingStop(int errorcode, QString last_error); + + bool StreamingActive(); + +private slots: + /* Stream action (start/stop) slot */ + void StreamActionTriggered(); + +signals: + /* Streaming signals */ + void StreamingPreparing(); + void StreamingStarting(bool broadcastAutoStart); + void StreamingStarted(bool withDelay = false); + void StreamingStopping(); + void StreamingStopped(bool withDelay = false); + + /* ------------------------------------- + * MARK: - OBSBasic_StudioMode + * ------------------------------------- + */ +private: + QPointer studioProgramProjector; + QPointer programWidget; + QPointer programLayout; + QPointer programLabel; + QPointer programOptions; + QPointer program; + OBSWeakSource lastProgramScene; + + bool editPropertiesMode = false; + bool sceneDuplicationMode = true; + + OBSWeakSource programScene; + volatile bool previewProgramMode = false; + + obs_hotkey_pair_id togglePreviewProgramHotkeys = 0; + + int programX = 0, programY = 0; + int programCX = 0, programCY = 0; + float programScale = 0.0f; + + void CreateProgramDisplay(); + void CreateProgramOptions(); + void SetPreviewProgramMode(bool enabled); + void ResizeProgram(uint32_t cx, uint32_t cy); + static void RenderProgram(void *data, uint32_t cx, uint32_t cy); + + void UpdatePreviewProgramIndicators(); + +private slots: + void EnablePreviewProgram(); + void DisablePreviewProgram(); + + void ProgramViewContextMenuRequested(); + + void OpenStudioProgramProjector(); + void OpenStudioProgramWindow(); + + /* Studio Mode toggle slot */ + void TogglePreviewProgramMode(); + +public: + OBSSource GetProgramSource(); + + inline bool IsPreviewProgramMode() const { return os_atomic_load_bool(&previewProgramMode); } + +signals: + /* Studio Mode signal */ + void PreviewProgramModeChanged(bool enabled); + + /* ------------------------------------- + * MARK: - OBSBasic_SysTray + * ------------------------------------- + */ +private: + QScopedPointer trayIcon; + QPointer sysTrayStream; + QPointer sysTrayRecord; + QPointer sysTrayReplayBuffer; + QPointer sysTrayVirtualCam; + QPointer trayMenu; + + bool sysTrayMinimizeToTray(); + +private slots: + void IconActivated(QSystemTrayIcon::ActivationReason reason); + +public: + void SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n); + + void SystemTrayInit(); + void SystemTray(bool firstStarted); + + /* ------------------------------------- + * MARK: - OBSBasic_Transitions + * ------------------------------------- + */ +private: + std::vector safeModeTransitions; + QPointer transitionButton; + QPointer perSceneTransitionMenu; + obs_source_t *fadeTransition; + obs_source_t *cutTransition; + std::vector quickTransitions; + bool swapScenesMode = true; + + obs_hotkey_id transitionHotkey = 0; + + int quickTransitionIdCounter = 1; + bool overridingTransition = false; + + QSlider *tBar; + bool tBarActive = false; + + OBSSource prevFTBSource = nullptr; + + void InitDefaultTransitions(); + void InitTransition(obs_source_t *transition); + obs_source_t *FindTransition(const char *name); + OBSSource GetCurrentTransition(); + obs_data_array_t *SaveTransitions(); + void LoadTransitions(obs_data_array_t *transitions, obs_load_source_cb cb, void *private_data); + + void AddQuickTransitionId(int id); + void AddQuickTransition(); + void AddQuickTransitionHotkey(QuickTransition *qt); + void RemoveQuickTransitionHotkey(QuickTransition *qt); + void LoadQuickTransitions(obs_data_array_t *array); + obs_data_array_t *SaveQuickTransitions(); + void ClearQuickTransitionWidgets(); + void RefreshQuickTransitions(); + // TODO: Remove orphaned method. + void DisableQuickTransitionWidgets(); + void EnableTransitionWidgets(bool enable); + void CreateDefaultQuickTransitions(); + + QuickTransition *GetQuickTransition(int id); + int GetQuickTransitionIdx(int id); + QMenu *CreateTransitionMenu(QWidget *parent, QuickTransition *qt); + void ClearQuickTransitions(); + void QuickTransitionClicked(); + void QuickTransitionChange(); + void QuickTransitionChangeDuration(int value); + void QuickTransitionRemoveClicked(); + + OBSSource GetOverrideTransition(OBSSource source); + int GetOverrideTransitionDuration(OBSSource source); + + QMenu *CreatePerSceneTransitionMenu(); + + void SetCurrentScene(obs_scene_t *scene, bool force = false); + + void PasteShowHideTransition(obs_sceneitem_t *item, bool show, obs_source_t *tr, int duration); + +public slots: + void SetCurrentScene(OBSSource scene, bool force = false); + + void SetTransition(OBSSource transition); + void OverrideTransition(OBSSource transition); + void TransitionToScene(OBSScene scene, bool force = false); + void TransitionToScene(OBSSource scene, bool force = false, bool quickTransition = false, int quickDuration = 0, + bool black = false, bool manual = false); + +private slots: + void AddTransition(const char *id); + void RenameTransition(OBSSource transition); + + void TransitionClicked(); + void TransitionStopped(); + void TransitionFullyStopped(); + void TriggerQuickTransition(int id); + + void TBarChanged(int value); + void TBarReleased(); + + void on_transitions_currentIndexChanged(int index); + void on_transitionAdd_clicked(); + void on_transitionRemove_clicked(); + void on_transitionProps_clicked(); + void on_transitionDuration_valueChanged(); + + void ShowTransitionProperties(); + void HideTransitionProperties(); + +public: + int GetTransitionDuration(); + int GetTbarPosition(); + + /* ------------------------------------- + * MARK: - OBSBasic_Updater + * ------------------------------------- + */ +private: + QScopedPointer whatsNewInitThread; + QScopedPointer updateCheckThread; + QScopedPointer introCheckThread; + + void TimedCheckForUpdates(); + void CheckForUpdates(bool manualUpdate); + + void MacBranchesFetched(const QString &branch, bool manualUpdate); + void ReceivedIntroJson(const QString &text); + void ShowWhatsNew(const QString &url); + + /* ------------------------------------- + * MARK: - OBSBasic_VirtualCam + * ------------------------------------- + */ +private: + bool vcamEnabled = false; + VCamConfig vcamConfig; + bool restartingVCam = false; + +public slots: + void StartVirtualCam(); + void StopVirtualCam(); + + void OnVirtualCamStart(); + void OnVirtualCamStop(int code); + + bool VirtualCamActive(); + +private slots: + void UpdateVirtualCamConfig(const VCamConfig &config); + void RestartVirtualCam(const VCamConfig &config); + void RestartingVirtualCam(); + + /* Virtual Cam action (start/stop) slots */ + void VirtualCamActionTriggered(); + + void OpenVirtualCamConfig(); + +public: + inline bool VCamEnabled() const { return vcamEnabled; } + +signals: + /* Virtual Camera signals */ + void VirtualCamEnabled(); + void VirtualCamStarted(); + void VirtualCamStopped(); + + /* ------------------------------------- + * MARK: - OBSBasic_VolControl + * ------------------------------------- + */ +private: + std::vector volumes; + + void UpdateVolumeControlsDecayRate(); + void UpdateVolumeControlsPeakMeterType(); + void ClearVolumeControls(); + void VolControlContextMenu(); + void ToggleVolControlLayout(); + void ToggleMixerLayout(bool vertical); + +private slots: + void HideAudioControl(); + void UnhideAllAudioControls(); + void ToggleHideMixer(); + + void on_vMixerScrollArea_customContextMenuRequested(); + void on_hMixerScrollArea_customContextMenuRequested(); + + void LockVolumeControl(bool lock); + + void on_actionMixerToolbarAdvAudio_triggered(); + void on_actionMixerToolbarMenu_triggered(); + + void StackedMixerAreaContextMenuRequested(); + +public: + void RefreshVolumeColors(); + + /* ------------------------------------- + * MARK: - OBSBasic_YouTube + * ------------------------------------- + */ + +private: + bool autoStartBroadcast = true; + bool autoStopBroadcast = true; + bool broadcastActive = false; + bool broadcastReady = false; + QPointer youtubeStreamCheckThread; + +#ifdef YOUTUBE_ENABLED + QPointer youtubeAppDock; + uint64_t lastYouTubeAppDockCreationTime = 0; + + void YoutubeStreamCheck(const std::string &key); + void ShowYouTubeAutoStartWarning(); + void YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, + bool autostart, bool autostop, bool start_now); +#endif + + void BroadcastButtonClicked(); + void SetBroadcastFlowEnabled(bool enabled); + +public: +#ifdef YOUTUBE_ENABLED + void NewYouTubeAppDock(); + void DeleteYouTubeAppDock(); + YouTubeAppDock *GetYouTubeAppDock(); +#endif + +public slots: + void SetupBroadcast(); + +signals: + /* Broadcast Flow signals */ + void BroadcastFlowEnabled(bool enabled); + void BroadcastStreamReady(bool ready); + void BroadcastStreamActive(); + void BroadcastStreamStarted(bool autoStop); }; diff --git a/frontend/widgets/OBSBasicStatusBar.cpp b/frontend/widgets/OBSBasicStatusBar.cpp index 5bcae6f33..e6d3fce66 100644 --- a/frontend/widgets/OBSBasicStatusBar.cpp +++ b/frontend/widgets/OBSBasicStatusBar.cpp @@ -1,14 +1,10 @@ -#include -#include -#include "obs-app.hpp" -#include "window-basic-main.hpp" -#include "moc_window-basic-status-bar.cpp" -#include "window-basic-main-outputs.hpp" -#include "qt-wrappers.hpp" -#include "platform.hpp" - +#include "OBSBasicStatusBar.hpp" #include "ui_StatusBarWidget.h" +#include + +#include "moc_OBSBasicStatusBar.cpp" + static constexpr int bitrateUpdateSeconds = 2; static constexpr int congestionUpdateSeconds = 4; static constexpr float excellentThreshold = 0.0f; @@ -16,13 +12,6 @@ static constexpr float goodThreshold = 0.3333f; static constexpr float mediocreThreshold = 0.6667f; static constexpr float badThreshold = 1.0f; -StatusBarWidget::StatusBarWidget(QWidget *parent) : QWidget(parent), ui(new Ui::StatusBarWidget) -{ - ui->setupUi(this); -} - -StatusBarWidget::~StatusBarWidget() {} - OBSBasicStatusBar::OBSBasicStatusBar(QWidget *parent) : QStatusBar(parent), excellentPixmap(QIcon(":/res/images/network-excellent.svg").pixmap(QSize(16, 16))), diff --git a/frontend/widgets/OBSBasicStatusBar.hpp b/frontend/widgets/OBSBasicStatusBar.hpp index 38be9e520..6ccc90fcc 100644 --- a/frontend/widgets/OBSBasicStatusBar.hpp +++ b/frontend/widgets/OBSBasicStatusBar.hpp @@ -1,25 +1,13 @@ #pragma once -#include +#include "StatusBarWidget.hpp" + +#include + #include -#include -#include -#include +#include -class Ui_StatusBarWidget; - -class StatusBarWidget : public QWidget { - Q_OBJECT - - friend class OBSBasicStatusBar; - -private: - std::unique_ptr ui; - -public: - StatusBarWidget(QWidget *parent = nullptr); - ~StatusBarWidget(); -}; +class QTimer; class OBSBasicStatusBar : public QStatusBar { Q_OBJECT diff --git a/frontend/widgets/OBSBasic_Browser.cpp b/frontend/widgets/OBSBasic_Browser.cpp index e822bb134..91cbca5b9 100644 --- a/frontend/widgets/OBSBasic_Browser.cpp +++ b/frontend/widgets/OBSBasic_Browser.cpp @@ -15,23 +15,28 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include "window-basic-main.hpp" - -#include +#include "OBSBasic.hpp" #ifdef BROWSER_AVAILABLE -#include +#include +#include + +#include +#include + +#include + +using namespace json11; #endif +#include + struct QCef; struct QCefCookieManager; -extern QCef *cef; -extern QCefCookieManager *panel_cookies; +QCef *cef = nullptr; +QCefCookieManager *panel_cookies = nullptr; +bool cef_js_avail = false; static std::string GenId() { diff --git a/frontend/widgets/OBSBasic_Clipboard.cpp b/frontend/widgets/OBSBasic_Clipboard.cpp index 47cac8dcc..655591fb7 100644 --- a/frontend/widgets/OBSBasic_Clipboard.cpp +++ b/frontend/widgets/OBSBasic_Clipboard.cpp @@ -16,7908 +16,14 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" -#include -#include -#include -#include -#include +#include +#include +#include -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} +extern void undo_redo(const std::string &data); void OBSBasic::on_actionCopyTransform_triggered() { @@ -7930,15 +36,6 @@ void OBSBasic::on_actionCopyTransform_triggered() hasCopiedTransform = true; } -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - void OBSBasic::on_actionPasteTransform_triggered() { OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); @@ -7968,1187 +65,6 @@ void OBSBasic::on_actionPasteTransform_triggered() undo_redo, undo_data, redo_data); } -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - void OBSBasic::on_actionCopySource_triggered() { clipboard.clear(); @@ -9331,873 +247,3 @@ void OBSBasic::on_actionPasteFilters_triggered() SourcePasteFilters(source.Get(), dstSource); } - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_ContextToolbar.cpp b/frontend/widgets/OBSBasic_ContextToolbar.cpp index 47cac8dcc..ccb3474a3 100644 --- a/frontend/widgets/OBSBasic_ContextToolbar.cpp +++ b/frontend/widgets/OBSBasic_ContextToolbar.cpp @@ -16,675 +16,23 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - void OBSBasic::copyActionsDynamicProperties() { // Themes need the QAction dynamic properties @@ -722,2510 +70,6 @@ void OBSBasic::copyActionsDynamicProperties() } } -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - void OBSBasic::ClearContextBar() { QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); @@ -3431,5426 +275,6 @@ void OBSBasic::UpdateContextBar(bool force) } } -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - void OBSBasic::ShowContextBar() { on_toggleContextBar_toggled(true); @@ -8869,1335 +293,3 @@ void OBSBasic::on_toggleContextBar_toggled(bool visible) this->ui->contextContainer->setVisible(visible); UpdateContextBar(true); } - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Docks.cpp b/frontend/widgets/OBSBasic_Docks.cpp index 47cac8dcc..c2bdaf7ec 100644 --- a/frontend/widgets/OBSBasic_Docks.cpp +++ b/frontend/widgets/OBSBasic_Docks.cpp @@ -16,251 +16,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" + #include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - void assignDockToggle(QDockWidget *dock, QAction *action) { auto handleWindowToggle = [action](bool vis) { @@ -300,8395 +60,6 @@ void setupDockAction(QDockWidget *dock) action->connect(action, &QAction::enabledChanged, neverDisable); } -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - void OBSBasic::on_resetDocks_triggered(bool force) { /* prune deleted extra docks */ @@ -8823,719 +194,6 @@ void OBSBasic::on_sideDocks_toggled(bool side) } } -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - QAction *OBSBasic::AddDockWidget(QDockWidget *dock) { // Prevent the object name from being changed @@ -9691,513 +349,3 @@ void OBSBasic::RepairCustomExtraDockName() dock->setObjectName(extraCustomDockNames[idx]); } - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Dropfiles.cpp b/frontend/widgets/OBSBasic_Dropfiles.cpp index 0e4713816..3f251951a 100644 --- a/frontend/widgets/OBSBasic_Dropfiles.cpp +++ b/frontend/widgets/OBSBasic_Dropfiles.cpp @@ -1,17 +1,32 @@ -#include -#include -#include -#include +/****************************************************************************** + Copyright (C) 2023 by Lain Bailey + Zachary Lund + Philippe Groarke + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "OBSBasic.hpp" + +#include + #include #include -#include #ifdef _WIN32 #include #endif -#include -#include - -#include "window-basic-main.hpp" +#include using namespace std; diff --git a/frontend/widgets/OBSBasic_Hotkeys.cpp b/frontend/widgets/OBSBasic_Hotkeys.cpp index 47cac8dcc..3be00aeee 100644 --- a/frontend/widgets/OBSBasic_Hotkeys.cpp +++ b/frontend/widgets/OBSBasic_Hotkeys.cpp @@ -16,2563 +16,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} +#include void OBSBasic::InitHotkeys() { @@ -2853,7104 +300,6 @@ void OBSBasic::ClearHotkeys() obs_hotkey_unregister(sourceScreenshotHotkey); } -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - void OBSBasic::ResetStatsHotkey() { const QList list = findChildren(); @@ -9959,245 +308,3 @@ void OBSBasic::ResetStatsHotkey() s->Reset(); } } - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Icons.cpp b/frontend/widgets/OBSBasic_Icons.cpp index eabda2a73..cf59dea7d 100644 --- a/frontend/widgets/OBSBasic_Icons.cpp +++ b/frontend/widgets/OBSBasic_Icons.cpp @@ -1,4 +1,23 @@ -#include +/****************************************************************************** + Copyright (C) 2023 by Lain Bailey + Zachary Lund + Philippe Groarke + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "OBSBasic.hpp" QIcon OBSBasic::GetSourceIcon(const char *id) const { diff --git a/frontend/widgets/OBSBasic_MainControls.cpp b/frontend/widgets/OBSBasic_MainControls.cpp index 47cac8dcc..6b3c7958c 100644 --- a/frontend/widgets/OBSBasic_MainControls.cpp +++ b/frontend/widgets/OBSBasic_MainControls.cpp @@ -16,3018 +16,52 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" +#include "OBSBasicStats.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __APPLE__ +#include +#endif +#include +#include +#ifdef _WIN32 +#include +#endif +#include +#if defined(_WIN32) || defined(WHATSNEW_ENABLED) +#include +#endif +#include + #include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include +#include #ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" +#include #endif -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" +extern bool restart; +extern bool restart_safe; +extern volatile long insideEventLoop; +extern bool safe_mode; struct QCef; struct QCefCookieManager; -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; +extern QCef *cef; +extern QCefCookieManager *panel_cookies; -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} +using namespace std; void OBSBasic::CreateInteractionWindow(obs_source_t *source) { @@ -3071,1374 +105,12 @@ void OBSBasic::CreateFiltersWindow(obs_source_t *source) filters->setAttribute(Qt::WA_DeleteOnClose, true); } -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - void OBSBasic::updateCheckFinished() { ui->actionCheckForUpdates->setEnabled(true); ui->actionRepair->setEnabled(true); } -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - void OBSBasic::ResetUI() { bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); @@ -4451,177 +123,6 @@ void OBSBasic::ResetUI() UpdatePreviewProgramIndicators(); } -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - void OBSBasic::CloseDialogs() { QList childDialogs = this->findChildren(); @@ -4660,293 +161,6 @@ void OBSBasic::EnumDialogs() } } -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - void OBSBasic::on_actionRemux_triggered() { if (!remux.isNull()) { @@ -5010,42 +224,6 @@ void OBSBasic::on_actionShowMacPermissions_triggered() #endif } -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - void OBSBasic::on_actionAdvAudioProperties_triggered() { if (advAudioWindow != nullptr) { @@ -5061,1039 +239,6 @@ void OBSBasic::on_actionAdvAudioProperties_triggered() advAudioWindow->SetIconsVisible(iconsVisible); } -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - static BPtr ReadLogFile(const char *subdir, const char *log) { char logDir[512]; @@ -6261,1321 +406,6 @@ void OBSBasic::openLogDialog(const QString &text, const bool crash) logDialog.exec(); } -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - void OBSBasic::on_actionHelpPortal_triggered() { QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); @@ -7639,56 +469,6 @@ void OBSBasic::on_actionShowProfileFolder_triggered() } } -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - void OBSBasic::on_actionAlwaysOnTop_triggered() { #ifndef _WIN32 @@ -7712,203 +492,6 @@ void OBSBasic::ToggleAlwaysOnTop() show(); } -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) { if (transformWindow) @@ -7919,722 +502,6 @@ void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); } -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - void OBSBasic::on_actionFullscreenInterface_triggered() { if (!isFullScreen()) @@ -8643,186 +510,6 @@ void OBSBasic::on_actionFullscreenInterface_triggered() showNormal(); } -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - void OBSBasic::on_resetUI_triggered() { on_resetDocks_triggered(); @@ -8837,11 +524,6 @@ void OBSBasic::on_resetUI_triggered() config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); } -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) { ui->sourcesToolbar->setVisible(visible); @@ -8851,25 +533,6 @@ void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); } -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - void OBSBasic::on_toggleStatusBar_toggled(bool visible) { ui->statusbar->setVisible(visible); @@ -8877,69 +540,6 @@ void OBSBasic::on_toggleStatusBar_toggled(bool visible) config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); } -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - void OBSBasic::SetShowing(bool showing) { if (!showing && isVisible()) { @@ -9014,131 +614,6 @@ void OBSBasic::ToggleShowHide() SetShowing(!showing); } -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - void OBSBasic::on_actionMainUndo_triggered() { undo_s.undo(); @@ -9149,327 +624,6 @@ void OBSBasic::on_actionMainRedo_triggered() undo_s.redo(); } -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - void OBSBasic::on_autoConfigure_triggered() { AutoConfig test(this); @@ -9503,463 +657,6 @@ void OBSBasic::on_actionShowAbout_triggered() about->setAttribute(Qt::WA_DeleteOnClose, true); } -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) { QWidget *widget = childAt(pos); @@ -9983,221 +680,3 @@ void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) ui->menuDocks->exec(globalPos); } } - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_OutputHandler.cpp b/frontend/widgets/OBSBasic_OutputHandler.cpp index 47cac8dcc..aa1778f53 100644 --- a/frontend/widgets/OBSBasic_OutputHandler.cpp +++ b/frontend/widgets/OBSBasic_OutputHandler.cpp @@ -16,1978 +16,12 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" + #include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; +#include void OBSBasic::ResetOutputs() { @@ -2013,2357 +47,6 @@ void OBSBasic::ResetOutputs() } } -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - bool OBSBasic::Active() const { if (!outputHandler) @@ -4371,5138 +54,6 @@ bool OBSBasic::Active() const return outputHandler->Active(); } -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - void OBSBasic::ResizeOutputSizeOfSource() { if (obs_video_active()) @@ -9536,337 +87,6 @@ void OBSBasic::ResizeOutputSizeOfSource() on_actionFitToScreen_triggered(); } -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - const char *OBSBasic::GetCurrentOutputPath() { const char *path = nullptr; @@ -9917,287 +137,3 @@ bool OBSBasic::OutputPathValid() const char *path = GetCurrentOutputPath(); return path && *path && QDir(path).exists(); } - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Preview.cpp b/frontend/widgets/OBSBasic_Preview.cpp index 47cac8dcc..f9f334ac6 100644 --- a/frontend/widgets/OBSBasic_Preview.cpp +++ b/frontend/widgets/OBSBasic_Preview.cpp @@ -16,1927 +16,22 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" + +#include +#include + #include -#include -#include -#include -#include -#include +#include -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include #include -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif +extern void undo_redo(const std::string &data); using namespace std; -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - void OBSBasic::InitPrimitives() { ProfileScope("OBSBasic::InitPrimitives"); @@ -1981,1035 +76,6 @@ void OBSBasic::InitPrimitives() obs_leave_graphics(); } -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - void OBSBasic::UpdatePreviewScalingMenu() { bool fixedScaling = ui->preview->IsFixedScaling(); @@ -3029,1213 +95,6 @@ void OBSBasic::UpdatePreviewScalingMenu() ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); } -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - void OBSBasic::DrawBackdrop(float cx, float cy) { if (!box) @@ -4341,253 +200,6 @@ void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) GS_DEBUG_MARKER_END(); } -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) { QSize targetSize; @@ -4622,3054 +234,11 @@ void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) previewY += float(PREVIEW_EDGE_SIZE); } -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - void OBSBasic::on_preview_customContextMenuRequested() { CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); } -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() { QMenu popup(this); @@ -7689,679 +258,6 @@ void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() popup.exec(QCursor::pos()); } -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - void OBSBasic::EnablePreviewDisplay(bool enable) { obs_display_set_enabled(ui->preview->GetDisplay(), enable); @@ -8393,16 +289,6 @@ void OBSBasic::DisablePreview() EnablePreviewDisplay(false); } -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) { if (obs_sceneitem_locked(item)) @@ -8488,404 +374,6 @@ void OBSBasic::Nudge(int dist, MoveDir dir) obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); } -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - void OBSBasic::on_actionLockPreview_triggered() { ui->preview->ToggleLocked(); @@ -8940,398 +428,6 @@ void OBSBasic::on_actionScaleOutput_triggered() emit ui->preview->DisplayResized(); } -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) { for (int x = 0; x < selectedItems.count(); x++) { @@ -9453,537 +549,6 @@ void OBSBasic::ColorChange() } } -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - void OBSBasic::UpdateProjectorHideCursor() { for (size_t i = 0; i < projectors.size(); i++) @@ -10004,35 +569,6 @@ void OBSBasic::ResetProjectors() OpenSavedProjectors(); } -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - void OBSBasic::UpdatePreviewSafeAreas() { drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); @@ -10049,35 +585,6 @@ void OBSBasic::UpdatePreviewOverflowSettings() ui->preview->SetOverflowAlwaysVisible(always); } -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - static inline QColor color_from_int(long long val) { return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); @@ -10120,12 +627,6 @@ float OBSBasic::GetDevicePixelRatio() return dpi; } -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - void OBSBasic::UpdatePreviewScrollbars() { if (!ui->preview->IsFixedScaling()) { @@ -10158,46 +659,3 @@ void OBSBasic::PreviewScalingModeChanged(int value) break; }; } - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Profiles.cpp b/frontend/widgets/OBSBasic_Profiles.cpp index 9b346e8d4..2e33dbcd1 100644 --- a/frontend/widgets/OBSBasic_Profiles.cpp +++ b/frontend/widgets/OBSBasic_Profiles.cpp @@ -15,21 +15,17 @@ along with this program. If not, see . ******************************************************************************/ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" + +#ifdef YOUTUBE_ENABLED +#include +#endif +#include + #include -#include "window-basic-main.hpp" -#include "window-basic-auto-config.hpp" -#include "window-namedialog.hpp" + +#include +#include // MARK: Constant Expressions @@ -38,6 +34,8 @@ constexpr std::string_view OBSProfileSettingsFile = "basic.ini"; // MARK: Forward Declarations +extern bool restart; + extern void DestroyPanelCookieManager(); extern void DuplicateCurrentCookieProfile(ConfigFile &config); extern void CheckExistingCookieId(); diff --git a/frontend/widgets/OBSBasic_Projectors.cpp b/frontend/widgets/OBSBasic_Projectors.cpp index 47cac8dcc..4ffe09bac 100644 --- a/frontend/widgets/OBSBasic_Projectors.cpp +++ b/frontend/widgets/OBSBasic_Projectors.cpp @@ -16,770 +16,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} +#include "OBSBasic.hpp" +#include "OBSProjector.hpp" obs_data_array_t *OBSBasic::SaveProjectors() { @@ -822,211 +61,6 @@ obs_data_array_t *OBSBasic::SaveProjectors() return savedProjectors; } -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) { for (SavedProjectorInfo *info : savedProjectorsArray) { @@ -1051,3615 +85,12 @@ void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) } } -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - void OBSBasic::UpdateMultiviewProjectorMenu() { ui->multiviewProjectorMenu->clear(); AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); } -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - void OBSBasic::ClearProjectors() { for (size_t i = 0; i < projectors.size(); i++) { @@ -4670,455 +101,6 @@ void OBSBasic::ClearProjectors() projectors.clear(); } -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - QList OBSBasic::GetProjectorMenuMonitorsFormatted() { QList projectorsFormatted; @@ -5150,3344 +132,6 @@ QList OBSBasic::GetProjectorMenuMonitorsFormatted() return projectorsFormatted; } -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - void OBSBasic::DeleteProjector(OBSProjector *projector) { for (size_t i = 0; i < projectors.size(); i++) { @@ -8522,12 +166,6 @@ OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, Project return projector; } -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - void OBSBasic::OpenPreviewProjector() { int monitor = sender()->property("monitor").toInt(); @@ -8560,11 +198,6 @@ void OBSBasic::OpenSceneProjector() OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); } -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - void OBSBasic::OpenPreviewWindow() { OpenProjector(nullptr, -1, ProjectorType::Preview); @@ -8635,1569 +268,7 @@ void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) } } -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - void OBSBasic::on_multiviewProjectorWindowed_triggered() { OpenProjector(nullptr, -1, ProjectorType::Multiview); } - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Recording.cpp b/frontend/widgets/OBSBasic_Recording.cpp index 47cac8dcc..83f030488 100644 --- a/frontend/widgets/OBSBasic_Recording.cpp +++ b/frontend/widgets/OBSBasic_Recording.cpp @@ -16,4924 +16,16 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" + +#include +#include + #include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} +#include +#include void OBSBasic::on_actionShow_Recordings_triggered() { @@ -4947,2027 +39,11 @@ void OBSBasic::on_actionShow_Recordings_triggered() QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - #define RECORDING_START "==== Recording Start ===============================================" #define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; extern volatile bool replaybuf_active; -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - void OBSBasic::AutoRemux(QString input, bool no_show) { auto config = Config(); @@ -7165,324 +241,6 @@ void OBSBasic::RecordingFileChanged(QString lastRecordingPath) AutoRemux(lastRecordingPath, true); } -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - void OBSBasic::RecordActionTriggered() { if (outputHandler->RecordingActive()) { @@ -7505,2205 +263,6 @@ void OBSBasic::RecordActionTriggered() } } -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - bool OBSBasic::RecordingActive() { if (!outputHandler) @@ -9711,58 +270,6 @@ bool OBSBasic::RecordingActive() return outputHandler->RecordingActive(); } -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - void OBSBasic::PauseRecording() { if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || @@ -9867,57 +374,6 @@ void OBSBasic::UpdateIsRecordingPausable() #define MBYTES_LEFT_STOP_REC 50ULL #define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - void OBSBasic::DiskSpaceMessage() { blog(LOG_ERROR, "Recording stopped because of low disk space"); @@ -9950,254 +406,3 @@ void OBSBasic::CheckDiskSpaceRemaining() DiskSpaceMessage(); } } - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_ReplayBuffer.cpp b/frontend/widgets/OBSBasic_ReplayBuffer.cpp index 47cac8dcc..8d95f2210 100644 --- a/frontend/widgets/OBSBasic_ReplayBuffer.cpp +++ b/frontend/widgets/OBSBasic_ReplayBuffer.cpp @@ -16,1970 +16,17 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" + +#include + #include -#include -#include -#include -#include -#include +#include -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} +#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" +#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" void OBSBasic::ReplayBufferActionTriggered() { @@ -1989,5182 +36,6 @@ void OBSBasic::ReplayBufferActionTriggered() StartReplayBuffer(); }; -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - void OBSBasic::ShowReplayBufferPauseWarning() { auto msgBox = []() { @@ -7339,2865 +210,9 @@ void OBSBasic::ReplayBufferStop(int code) OnDeactivate(); } -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - bool OBSBasic::ReplayBufferActive() { if (!outputHandler) return false; return outputHandler->ReplayBufferActive(); } - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_SceneItems.cpp b/frontend/widgets/OBSBasic_SceneItems.cpp index 47cac8dcc..f28e3d07c 100644 --- a/frontend/widgets/OBSBasic_SceneItems.cpp +++ b/frontend/widgets/OBSBasic_SceneItems.cpp @@ -16,919 +16,25 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" +#include "ColorSelect.hpp" +#include "OBSProjector.hpp" +#include "VolControl.hpp" + +#include +#include +#include +#include + #include -#include -#include -#include -#include -#include +#include -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include #include -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - using namespace std; -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - static inline bool HasAudioDevices(const char *source_id) { const char *output_id = source_id; @@ -966,2040 +72,6 @@ void OBSBasic::CreateFirstRunSources() ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); } -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) { return item ? GetOBSRef(item) : nullptr; @@ -3010,195 +82,6 @@ OBSSceneItem OBSBasic::GetCurrentSceneItem() return ui->sources->Get(GetTopSelectedSourceItem()); } -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) { QList items = listWidget->findItems(prevName, Qt::MatchExactly); @@ -3226,225 +109,6 @@ void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName UpdatePreviewProgramIndicators(); } -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - void OBSBasic::GetAudioSourceFilters() { QAction *action = reinterpret_cast(sender()); @@ -3463,56 +127,6 @@ void OBSBasic::GetAudioSourceProperties() CreatePropertiesWindow(source); } -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - void OBSBasic::MixerRenameSource() { QAction *action = reinterpret_cast(sender()); @@ -3545,187 +159,6 @@ void OBSBasic::MixerRenameSource() } } -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - void OBSBasic::ActivateAudioSource(OBSSource source) { if (SourceMixerHidden(source)) @@ -3813,316 +246,6 @@ bool OBSBasic::QueryRemoveSource(obs_source_t *source) return Yes == remove_source.clickedButton(); } -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - void OBSBasic::ReorderSources(OBSScene scene) { if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) @@ -4141,35 +264,6 @@ void OBSBasic::RefreshSources(OBSScene scene) SaveProject(); } -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - void OBSBasic::SourceCreated(void *data, calldata_t *params) { obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); @@ -4236,326 +330,6 @@ void OBSBasic::SourceRenamed(void *data, calldata_t *params) blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); } -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - extern char *get_new_source_name(const char *name, const char *format); void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) @@ -4588,785 +362,6 @@ void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, cons } } -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - void OBSBasic::SetDeinterlacingMode() { QAction *action = reinterpret_cast(sender()); @@ -5565,11 +560,6 @@ QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction return menu; } -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) { QMenu popup(this); @@ -5725,20 +715,6 @@ void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) } } -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - static inline bool should_show_properties(obs_source_t *source, const char *id) { if (!source) @@ -5878,82 +854,6 @@ static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) return true; }; -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - void OBSBasic::on_actionRemoveSource_triggered() { vector items; @@ -6040,30 +940,6 @@ void OBSBasic::on_actionSourceProperties_triggered() CreatePropertiesWindow(source); } -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - void OBSBasic::on_actionSourceUp_triggered() { MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); @@ -6094,226 +970,6 @@ void OBSBasic::on_actionMoveToBottom_triggered() MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); } -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - void OBSBasic::OpenFilters(OBSSource source) { if (source == nullptr) { @@ -6350,1295 +1006,6 @@ void OBSBasic::OpenEditTransform(OBSSceneItem item) CreateEditTransformWindow(item); } -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - int OBSBasic::GetTopSelectedSourceItem() { QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); @@ -7650,257 +1017,6 @@ QModelIndexList OBSBasic::GetAllSelectedSourceItems() return ui->sources->selectionModel()->selectedIndexes(); } -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - void OBSBasic::on_actionEditTransform_triggered() { const auto item = GetCurrentSceneItem(); @@ -7909,27 +1025,6 @@ void OBSBasic::on_actionEditTransform_triggered() CreateEditTransformWindow(item); } -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - void undo_redo(const std::string &data) { OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); @@ -7939,81 +1034,6 @@ void undo_redo(const std::string &data) obs_scene_load_transform_states(data.c_str()); } -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) { matrix4 boxTransform; @@ -8362,521 +1382,6 @@ void OBSBasic::on_actionHorizontalCenter_triggered() undo_redo, undo_redo, undo_data, redo_data); } -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - void OBSBasic::on_toggleSourceIcons_toggled(bool visible) { ui->sources->SetIconsVisible(visible); @@ -8886,1124 +1391,6 @@ void OBSBasic::on_toggleSourceIcons_toggled(bool visible) config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); } -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - void OBSBasic::on_sourcePropertiesButton_clicked() { on_actionSourceProperties_triggered(); @@ -10014,190 +1401,7 @@ void OBSBasic::on_sourceFiltersButton_clicked() OpenFilters(); } -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - void OBSBasic::on_sourceInteractButton_clicked() { on_actionInteract_triggered(); } - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Scenes.cpp b/frontend/widgets/OBSBasic_Scenes.cpp index 47cac8dcc..ffc029ce4 100644 --- a/frontend/widgets/OBSBasic_Scenes.cpp +++ b/frontend/widgets/OBSBasic_Scenes.cpp @@ -16,757 +16,33 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" +#include "OBSProjector.hpp" + +#include + #include -#include -#include -#include -#include -#include +#include +#include -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif +#include using namespace std; -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - namespace { -QPointer obsWhatsNew; - template struct SignalContainer { OBSRef ref; vector> handlers; }; } // namespace -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); Q_DECLARE_METATYPE(obs_order_movement); Q_DECLARE_METATYPE(SignalContainer); -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} +extern void undo_redo(const std::string &data); obs_data_array_t *OBSBasic::SaveSceneListOrder() { @@ -781,225 +57,6 @@ obs_data_array_t *OBSBasic::SaveSceneListOrder() return sceneOrder; } -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) { for (int i = 0; i < lw->count(); i++) { @@ -1027,2052 +84,11 @@ void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) } } -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - OBSScene OBSBasic::GetCurrentScene() { return currentScene.load(); } -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - void OBSBasic::AddScene(OBSSource source) { const char *name = obs_source_get_name(source); @@ -3199,697 +215,6 @@ void OBSBasic::AddSceneItem(OBSSceneItem item) } } -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - void OBSBasic::DuplicateSelectedScene() { OBSScene curScene = GetCurrentScene(); @@ -4123,26 +448,6 @@ void OBSBasic::RemoveSelectedScene() OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); } -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - void OBSBasic::SceneReordered(void *data, calldata_t *params) { OBSBasic *window = static_cast(data); @@ -4170,921 +475,6 @@ void OBSBasic::SceneItemAdded(void *data, calldata_t *params) QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); } -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) { OBSSource source; @@ -5119,37 +509,6 @@ void OBSBasic::EditSceneName() item->setFlags(flags); } -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) { QListWidgetItem *item = ui->scenes->itemAt(pos); @@ -5367,364 +726,6 @@ void OBSBasic::EditSceneItemName() ui->sources->Edit(idx); } -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) { if (!witem) @@ -5739,145 +740,6 @@ void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) } } -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) { OBSDataArrayAutoRelease undo_array = obs_data_array_create(); @@ -5954,92 +816,6 @@ void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData und undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); } -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) { OBSSceneItem item = GetCurrentSceneItem(); @@ -6064,203 +840,6 @@ void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &ac CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); } -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) { const char *prevName = obs_source_get_name(source); @@ -6314,42 +893,6 @@ void OBSBasic::SceneNameEdited(QWidget *editor) OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); } -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - void OBSBasic::OpenSceneFilters() { OBSScene scene = GetCurrentScene(); @@ -6358,1616 +901,6 @@ void OBSBasic::OpenSceneFilters() CreateFiltersWindow(source); } -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) { if (obs_sceneitem_is_group(item)) @@ -8014,1445 +947,6 @@ void OBSBasic::on_actionResetTransform_triggered() obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); } -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) { int i = 0; @@ -9470,550 +964,6 @@ SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) return nullptr; } -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - void OBSBasic::on_actionSceneFilters_triggered() { OBSSource sceneSource = GetCurrentSceneSource(); @@ -10021,183 +971,3 @@ void OBSBasic::on_actionSceneFilters_triggered() if (sceneSource) OpenFilters(sceneSource); } - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Screenshots.cpp b/frontend/widgets/OBSBasic_Screenshots.cpp index a0b3622d1..aee070f97 100644 --- a/frontend/widgets/OBSBasic_Screenshots.cpp +++ b/frontend/widgets/OBSBasic_Screenshots.cpp @@ -15,305 +15,12 @@ along with this program. If not, see . ******************************************************************************/ -#include "window-basic-main.hpp" -#include "screenshot-obj.hpp" +#include "OBSBasic.hpp" + +#include #include -#ifdef _WIN32 -#include -#include -#include -#pragma comment(lib, "windowscodecs.lib") -#endif - -static void ScreenshotTick(void *param, float); - -/* ========================================================================= */ - -ScreenshotObj::ScreenshotObj(obs_source_t *source) : weakSource(OBSGetWeakRef(source)) -{ - obs_add_tick_callback(ScreenshotTick, this); -} - -ScreenshotObj::~ScreenshotObj() -{ - obs_enter_graphics(); - gs_stagesurface_destroy(stagesurf); - gs_texrender_destroy(texrender); - obs_leave_graphics(); - - obs_remove_tick_callback(ScreenshotTick, this); - - if (th.joinable()) { - th.join(); - - if (cx && cy) { - OBSBasic *main = OBSBasic::Get(); - main->ShowStatusBarMessage( - QTStr("Basic.StatusBar.ScreenshotSavedTo").arg(QT_UTF8(path.c_str()))); - - main->lastScreenshot = path; - - main->OnEvent(OBS_FRONTEND_EVENT_SCREENSHOT_TAKEN); - } - } -} - -void ScreenshotObj::Screenshot() -{ - OBSSource source = OBSGetStrongRef(weakSource); - - if (source) { - cx = obs_source_get_width(source); - cy = obs_source_get_height(source); - } else { - obs_video_info ovi; - obs_get_video_info(&ovi); - cx = ovi.base_width; - cy = ovi.base_height; - } - - if (!cx || !cy) { - blog(LOG_WARNING, "Cannot screenshot, invalid target size"); - obs_remove_tick_callback(ScreenshotTick, this); - deleteLater(); - return; - } - -#ifdef _WIN32 - enum gs_color_space space = obs_source_get_color_space(source, 0, nullptr); - if (space == GS_CS_709_EXTENDED) { - /* Convert for JXR */ - space = GS_CS_709_SCRGB; - } -#else - /* Tonemap to SDR if HDR */ - const enum gs_color_space space = GS_CS_SRGB; -#endif - const enum gs_color_format format = gs_get_format_from_space(space); - - texrender = gs_texrender_create(format, GS_ZS_NONE); - stagesurf = gs_stagesurface_create(cx, cy, format); - - if (gs_texrender_begin_with_color_space(texrender, cx, cy, space)) { - vec4 zero; - vec4_zero(&zero); - - gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0); - gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f); - - gs_blend_state_push(); - gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); - - if (source) { - obs_source_inc_showing(source); - obs_source_video_render(source); - obs_source_dec_showing(source); - } else { - obs_render_main_texture(); - } - - gs_blend_state_pop(); - gs_texrender_end(texrender); - } -} - -void ScreenshotObj::Download() -{ - gs_stage_texture(stagesurf, gs_texrender_get_texture(texrender)); -} - -void ScreenshotObj::Copy() -{ - uint8_t *videoData = nullptr; - uint32_t videoLinesize = 0; - - if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) { - if (gs_stagesurface_get_color_format(stagesurf) == GS_RGBA16F) { - const uint32_t linesize = cx * 8; - half_bytes.reserve(cx * cy * 8); - - for (uint32_t y = 0; y < cy; y++) { - const uint8_t *const line = videoData + (y * videoLinesize); - half_bytes.insert(half_bytes.end(), line, line + linesize); - } - } else { - image = QImage(cx, cy, QImage::Format::Format_RGBX8888); - - int linesize = image.bytesPerLine(); - for (int y = 0; y < (int)cy; y++) - memcpy(image.scanLine(y), videoData + (y * videoLinesize), linesize); - } - - gs_stagesurface_unmap(stagesurf); - } -} - -void ScreenshotObj::Save() -{ - OBSBasic *main = OBSBasic::Get(); - config_t *config = main->Config(); - - const char *mode = config_get_string(config, "Output", "Mode"); - const char *type = config_get_string(config, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") ? config_get_string(config, "AdvOut", "FFFilePath") - : config_get_string(config, "AdvOut", "RecFilePath"); - const char *rec_path = strcmp(mode, "Advanced") ? config_get_string(config, "SimpleOutput", "FilePath") - : adv_path; - - bool noSpace = config_get_bool(config, "SimpleOutput", "FileNameWithoutSpace"); - const char *filenameFormat = config_get_string(config, "Output", "FilenameFormatting"); - bool overwriteIfExists = config_get_bool(config, "Output", "OverwriteIfExists"); - - const char *ext = half_bytes.empty() ? "png" : "jxr"; - path = GetOutputFilename(rec_path, ext, noSpace, overwriteIfExists, - GetFormatString(filenameFormat, "Screenshot", nullptr).c_str()); - - th = std::thread([this] { MuxAndFinish(); }); -} - -#ifdef _WIN32 -static HRESULT SaveJxrImage(LPCWSTR path, uint8_t *pixels, uint32_t cx, uint32_t cy, IWICBitmapFrameEncode *frameEncode, - IPropertyBag2 *options) -{ - wchar_t lossless[] = L"Lossless"; - PROPBAG2 bag = {}; - bag.pstrName = lossless; - VARIANT value = {}; - value.vt = VT_BOOL; - value.bVal = TRUE; - HRESULT hr = options->Write(1, &bag, &value); - if (FAILED(hr)) - return hr; - - hr = frameEncode->Initialize(options); - if (FAILED(hr)) - return hr; - - hr = frameEncode->SetSize(cx, cy); - if (FAILED(hr)) - return hr; - - hr = frameEncode->SetResolution(72, 72); - if (FAILED(hr)) - return hr; - - WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat64bppRGBAHalf; - hr = frameEncode->SetPixelFormat(&pixelFormat); - if (FAILED(hr)) - return hr; - - if (memcmp(&pixelFormat, &GUID_WICPixelFormat64bppRGBAHalf, sizeof(WICPixelFormatGUID)) != 0) - return E_FAIL; - - hr = frameEncode->WritePixels(cy, cx * 8, cx * cy * 8, pixels); - if (FAILED(hr)) - return hr; - - hr = frameEncode->Commit(); - if (FAILED(hr)) - return hr; - - return S_OK; -} - -static HRESULT SaveJxr(LPCWSTR path, uint8_t *pixels, uint32_t cx, uint32_t cy) -{ - Microsoft::WRL::ComPtr factory; - HRESULT hr = CoCreateInstance(CLSID_WICImagingFactory, NULL, CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(factory.GetAddressOf())); - if (FAILED(hr)) - return hr; - - Microsoft::WRL::ComPtr stream; - hr = factory->CreateStream(stream.GetAddressOf()); - if (FAILED(hr)) - return hr; - - hr = stream->InitializeFromFilename(path, GENERIC_WRITE); - if (FAILED(hr)) - return hr; - - Microsoft::WRL::ComPtr encoder = NULL; - hr = factory->CreateEncoder(GUID_ContainerFormatWmp, NULL, encoder.GetAddressOf()); - if (FAILED(hr)) - return hr; - - hr = encoder->Initialize(stream.Get(), WICBitmapEncoderNoCache); - if (FAILED(hr)) - return hr; - - Microsoft::WRL::ComPtr frameEncode; - Microsoft::WRL::ComPtr options; - hr = encoder->CreateNewFrame(frameEncode.GetAddressOf(), options.GetAddressOf()); - if (FAILED(hr)) - return hr; - - hr = SaveJxrImage(path, pixels, cx, cy, frameEncode.Get(), options.Get()); - if (FAILED(hr)) - return hr; - - encoder->Commit(); - return S_OK; -} -#endif // #ifdef _WIN32 - -void ScreenshotObj::MuxAndFinish() -{ - if (half_bytes.empty()) { - image.save(QT_UTF8(path.c_str())); - blog(LOG_INFO, "Saved screenshot to '%s'", path.c_str()); - } else { -#ifdef _WIN32 - wchar_t *path_w = nullptr; - os_utf8_to_wcs_ptr(path.c_str(), 0, &path_w); - if (path_w) { - SaveJxr(path_w, half_bytes.data(), cx, cy); - bfree(path_w); - } -#endif // #ifdef _WIN32 - } - - deleteLater(); -} - -/* ========================================================================= */ - -#define STAGE_SCREENSHOT 0 -#define STAGE_DOWNLOAD 1 -#define STAGE_COPY_AND_SAVE 2 -#define STAGE_FINISH 3 - -static void ScreenshotTick(void *param, float) -{ - ScreenshotObj *data = reinterpret_cast(param); - - if (data->stage == STAGE_FINISH) { - return; - } - - obs_enter_graphics(); - - switch (data->stage) { - case STAGE_SCREENSHOT: - data->Screenshot(); - break; - case STAGE_DOWNLOAD: - data->Download(); - break; - case STAGE_COPY_AND_SAVE: - data->Copy(); - QMetaObject::invokeMethod(data, "Save"); - obs_remove_tick_callback(ScreenshotTick, data); - break; - } - - obs_leave_graphics(); - - data->stage++; -} - void OBSBasic::Screenshot(OBSSource source) { if (!!screenshotData) { diff --git a/frontend/widgets/OBSBasic_Service.cpp b/frontend/widgets/OBSBasic_Service.cpp index 47cac8dcc..ee201bc4d 100644 --- a/frontend/widgets/OBSBasic_Service.cpp +++ b/frontend/widgets/OBSBasic_Service.cpp @@ -16,1477 +16,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} +#include "OBSBasic.hpp" constexpr std::string_view OBSServiceFileName = "service.json"; @@ -1574,2775 +105,6 @@ bool OBSBasic::InitService() return true; } -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - obs_service_t *OBSBasic::GetService() { if (!service) { @@ -4358,5846 +120,3 @@ void OBSBasic::SetService(obs_service_t *newService) service = newService; } } - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_StatusBar.cpp b/frontend/widgets/OBSBasic_StatusBar.cpp index 47cac8dcc..a13229809 100644 --- a/frontend/widgets/OBSBasic_StatusBar.cpp +++ b/frontend/widgets/OBSBasic_StatusBar.cpp @@ -16,10188 +16,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} +#include "OBSBasic.hpp" void OBSBasic::ShowStatusBarMessage(const QString &message) { ui->statusbar->clearMessage(); ui->statusbar->showMessage(message, 10000); } - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Streaming.cpp b/frontend/widgets/OBSBasic_Streaming.cpp index 47cac8dcc..db5a30046 100644 --- a/frontend/widgets/OBSBasic_Streaming.cpp +++ b/frontend/widgets/OBSBasic_Streaming.cpp @@ -16,6356 +16,19 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" + +#include +#ifdef YOUTUBE_ENABLED +#include +#include +#endif + #include -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" #define STREAMING_START "==== Streaming Start ===============================================" #define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" void OBSBasic::DisplayStreamStartError() { @@ -6382,104 +45,6 @@ void OBSBasic::DisplayStreamStartError() QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); } -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - void OBSBasic::StartStreaming() { if (outputHandler->StreamingActive()) @@ -6556,179 +121,6 @@ void OBSBasic::StartStreaming() setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); } -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - void OBSBasic::StopStreaming() { SaveProject(); @@ -6968,445 +360,6 @@ void OBSBasic::StreamingStop(int code, QString last_error) SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); } -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - void OBSBasic::StreamActionTriggered() { if (outputHandler->StreamingActive()) { @@ -7483,2721 +436,9 @@ void OBSBasic::StreamActionTriggered() } } -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - bool OBSBasic::StreamingActive() { if (!outputHandler) return false; return outputHandler->StreamingActive(); } - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_SysTray.cpp b/frontend/widgets/OBSBasic_SysTray.cpp index 47cac8dcc..ef74a2a4c 100644 --- a/frontend/widgets/OBSBasic_SysTray.cpp +++ b/frontend/widgets/OBSBasic_SysTray.cpp @@ -16,9003 +16,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} +extern bool opt_minimize_tray; void OBSBasic::SystemTrayInit() { @@ -9138,1066 +145,3 @@ bool OBSBasic::sysTrayMinimizeToTray() { return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); } - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_Updater.cpp b/frontend/widgets/OBSBasic_Updater.cpp index 47cac8dcc..b13221128 100644 --- a/frontend/widgets/OBSBasic_Updater.cpp +++ b/frontend/widgets/OBSBasic_Updater.cpp @@ -16,116 +16,39 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include +#include #ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" +#include #endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - #ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" +#include +#include #endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include +#if defined(_WIN32) || defined(WHATSNEW_ENABLED) +#include +#include #endif -using namespace std; - #ifdef BROWSER_AVAILABLE #include #endif +#include -#include "ui-config.h" +#ifdef _WIN32 +#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ +#endif struct QCef; struct QCefCookieManager; -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; +extern QCef *cef; +extern QCefCookieManager *panel_cookies; -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); +using namespace std; namespace { @@ -137,2331 +60,6 @@ template struct SignalContainer { }; } // namespace -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ void OBSBasic::ReceivedIntroJson(const QString &text) { #ifdef WHATSNEW_ENABLED @@ -2568,1253 +166,6 @@ void OBSBasic::ShowWhatsNew(const QString &url) #endif } -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - void OBSBasic::TimedCheckForUpdates() { if (App()->IsUpdaterDisabled()) @@ -3883,6321 +234,3 @@ void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) UNUSED_PARAMETER(manualUpdate); #endif } - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_VirtualCam.cpp b/frontend/widgets/OBSBasic_VirtualCam.cpp index 47cac8dcc..3ccb535e6 100644 --- a/frontend/widgets/OBSBasic_VirtualCam.cpp +++ b/frontend/widgets/OBSBasic_VirtualCam.cpp @@ -16,7329 +16,15 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" -#include -#include -#include -#include -#include +#include +#include -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif - -using namespace std; - -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" #define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" #define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - void OBSBasic::StartVirtualCam() { if (!outputHandler || !outputHandler->virtualCam) @@ -7407,104 +93,6 @@ void OBSBasic::OnVirtualCamStop(int) QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); } -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - void OBSBasic::VirtualCamActionTriggered() { if (outputHandler->VirtualCamActive()) { @@ -7576,2628 +164,9 @@ void OBSBasic::RestartingVirtualCam() restartingVCam = false; } -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - bool OBSBasic::VirtualCamActive() { if (!outputHandler) return false; return outputHandler->VirtualCamActive(); } - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_VolControl.cpp b/frontend/widgets/OBSBasic_VolControl.cpp index 47cac8dcc..674d84829 100644 --- a/frontend/widgets/OBSBasic_VolControl.cpp +++ b/frontend/widgets/OBSBasic_VolControl.cpp @@ -16,712 +16,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" -#ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" -#endif - -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif +#include using namespace std; -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - void OBSBasic::UpdateVolumeControlsDecayRate() { double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); @@ -768,2701 +69,6 @@ void OBSBasic::RefreshVolumeColors() } } -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - void OBSBasic::HideAudioControl() { QAction *action = reinterpret_cast(sender()); @@ -3513,46 +119,6 @@ void OBSBasic::ToggleHideMixer() } } -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - void OBSBasic::LockVolumeControl(bool lock) { QAction *action = reinterpret_cast(sender()); @@ -3726,1341 +292,6 @@ void OBSBasic::ToggleVolControlLayout() ActivateAudioSource(source); } -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() { on_actionAdvAudioProperties_triggered(); @@ -5084,5120 +315,3 @@ void OBSBasic::on_actionMixerToolbarMenu_triggered() popup.addAction(&toggleControlLayoutAction); popup.exec(QCursor::pos()); } - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} - -#ifdef YOUTUBE_ENABLED -void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, - bool autostart, bool autostop, bool start_now) -{ - //blog(LOG_DEBUG, "Stream key: %s", QT_TO_UTF8(key)); - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - - const std::string a_key = QT_TO_UTF8(key); - obs_data_set_string(settings, "key", a_key.c_str()); - - const std::string b_id = QT_TO_UTF8(broadcast_id); - obs_data_set_string(settings, "broadcast_id", b_id.c_str()); - - const std::string s_id = QT_TO_UTF8(stream_id); - obs_data_set_string(settings, "stream_id", s_id.c_str()); - - obs_service_update(service_obj, settings); - autoStartBroadcast = autostart; - autoStopBroadcast = autostop; - broadcastReady = true; - - emit BroadcastStreamReady(broadcastReady); - - if (start_now) - QMetaObject::invokeMethod(this, "StartStreaming"); -} - -void OBSBasic::YoutubeStreamCheck(const std::string &key) -{ - YoutubeApiWrappers *apiYouTube(dynamic_cast(GetAuth())); - if (!apiYouTube) { - /* technically we should never get here -Lain */ - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - youtubeStreamCheckThread->deleteLater(); - blog(LOG_ERROR, "=========================================="); - blog(LOG_ERROR, "%s: Uh, hey, we got here", __FUNCTION__); - blog(LOG_ERROR, "=========================================="); - return; - } - - int timeout = 0; - json11::Json json; - QString id = key.c_str(); - - while (StreamingActive()) { - if (timeout == 14) { - QMetaObject::invokeMethod(this, "ForceStopStreaming", Qt::QueuedConnection); - break; - } - - if (!apiYouTube->FindStream(id, json)) { - QMetaObject::invokeMethod(this, "DisplayStreamStartError", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "StopStreaming", Qt::QueuedConnection); - break; - } - - auto item = json["items"][0]; - auto status = item["status"]["streamStatus"].string_value(); - if (status == "active") { - emit BroadcastStreamActive(); - break; - } else { - QThread::sleep(1); - timeout++; - } - } - - youtubeStreamCheckThread->deleteLater(); -} - -void OBSBasic::ShowYouTubeAutoStartWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("YouTube.Actions.AutoStartStreamingWarning.Title")); - msgbox.setText(QTStr("YouTube.Actions.AutoStartStreamingWarning")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutYouTubeAutoStart"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} -#endif - -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - -void OBSBasic::BroadcastButtonClicked() -{ - if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { - SetupBroadcast(); - return; - } - - if (!autoStartBroadcast) { -#ifdef YOUTUBE_ENABLED - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StartLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStartFailed"), last_error, true); - return; - } - } -#endif - broadcastActive = true; - autoStartBroadcast = true; // and clear the flag - - emit BroadcastStreamStarted(autoStopBroadcast); - } else if (!autoStopBroadcast) { -#ifdef YOUTUBE_ENABLED - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - std::shared_ptr ytAuth = dynamic_pointer_cast(auth); - if (ytAuth.get()) { - if (!ytAuth->StopLatestBroadcast()) { - auto last_error = ytAuth->GetLastError(); - if (last_error.isEmpty()) - last_error = QTStr("YouTube.Actions.Error.YouTubeApi"); - if (!ytAuth->GetTranslatedError(last_error)) - last_error = QTStr("YouTube.Actions.Error.BroadcastTransitionFailed") - .arg(last_error, ytAuth->GetBroadcastId()); - - OBSMessageBox::warning(this, QTStr("Output.BroadcastStopFailed"), last_error, true); - } - } -#endif - broadcastActive = false; - broadcastReady = false; - - autoStopBroadcast = true; - QMetaObject::invokeMethod(this, "StopStreaming"); - emit BroadcastStreamReady(broadcastReady); - SetBroadcastFlowEnabled(true); - } -} - -void OBSBasic::SetBroadcastFlowEnabled(bool enabled) -{ - emit BroadcastFlowEnabled(enabled); -} - -void OBSBasic::SetupBroadcast() -{ -#ifdef YOUTUBE_ENABLED - Auth *const auth = GetAuth(); - if (IsYouTubeService(auth->service())) { - OBSYoutubeActions dialog(this, auth, broadcastReady); - connect(&dialog, &OBSYoutubeActions::ok, this, &OBSBasic::YouTubeActionDialogOk); - dialog.exec(); - } -#endif -} - -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - -#ifdef YOUTUBE_ENABLED -YouTubeAppDock *OBSBasic::GetYouTubeAppDock() -{ - return youtubeAppDock; -} - -#ifndef SEC_TO_NSEC -#define SEC_TO_NSEC 1000000000 -#endif - -void OBSBasic::NewYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - /* make sure that the youtube app dock can't be immediately recreated. - * dumb hack. blame chromium. or this particular dock. or both. if CEF - * creates/destroys/creates a widget too quickly it can lead to a - * crash. */ - uint64_t ts = os_gettime_ns(); - if ((ts - lastYouTubeAppDockCreationTime) < (5ULL * SEC_TO_NSEC)) - return; - - lastYouTubeAppDockCreationTime = ts; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = new YouTubeAppDock("YouTube Live Control Panel"); -} - -void OBSBasic::DeleteYouTubeAppDock() -{ - if (!cef_js_avail) - return; - - if (youtubeAppDock) - RemoveDockWidget(youtubeAppDock->objectName()); - - youtubeAppDock = nullptr; -} -#endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSBasic_YouTube.cpp b/frontend/widgets/OBSBasic_YouTube.cpp index 47cac8dcc..bd708bb3f 100644 --- a/frontend/widgets/OBSBasic_YouTube.cpp +++ b/frontend/widgets/OBSBasic_YouTube.cpp @@ -16,6371 +16,20 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . ******************************************************************************/ -#include "ui-config.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "OBSBasic.hpp" -#include -#include -#include -#include -#include - -#include "obs-app.hpp" -#include "platform.hpp" -#include "visibility-item-widget.hpp" -#include "item-widget-helpers.hpp" -#include "basic-controls.hpp" -#include "window-basic-settings.hpp" -#include "window-namedialog.hpp" -#include "window-basic-auto-config.hpp" -#include "window-basic-source-select.hpp" -#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" -#ifdef __APPLE__ -#include "window-permissions.hpp" -#endif -#include "window-projector.hpp" -#include "window-remux.hpp" #ifdef YOUTUBE_ENABLED -#include "auth-youtube.hpp" -#include "window-youtube-actions.hpp" -#include "youtube-api-wrappers.hpp" -#endif -#include "window-whats-new.hpp" -#include "context-bar-controls.hpp" -#include "obs-proxy-style.hpp" -#include "display-helpers.hpp" -#include "volume-control.hpp" -#include "remote-text.hpp" -#include "ui-validation.hpp" -#include "media-controls.hpp" -#include "undo-stack-obs.hpp" -#include -#include - -#ifdef _WIN32 -#include "update/win-update.hpp" -#include "update/shared-update.hpp" -#include "windows.h" +#include +#include +#include #endif -#ifdef WHATSNEW_ENABLED -#include "update/models/whatsnew.hpp" -#endif - -#if !defined(_WIN32) && defined(WHATSNEW_ENABLED) -#include "update/shared-update.hpp" -#endif - -#ifdef ENABLE_SPARKLE_UPDATER -#include "update/mac-update.hpp" -#endif - -#include "ui_OBSBasic.h" -#include "ui_ColorSelect.h" - -#include - -#ifdef ENABLE_WAYLAND -#include -#endif +#include using namespace std; -#ifdef BROWSER_AVAILABLE -#include -#endif - -#include "ui-config.h" - -struct QCef; -struct QCefCookieManager; - -QCef *cef = nullptr; -QCefCookieManager *panel_cookies = nullptr; -bool cef_js_avail = false; - -extern std::string opt_starting_profile; -extern std::string opt_starting_collection; - -void DestroyPanelCookieManager(); - -namespace { - -QPointer obsWhatsNew; - -template struct SignalContainer { - OBSRef ref; - vector> handlers; -}; -} // namespace - -extern volatile long insideEventLoop; - -Q_DECLARE_METATYPE(OBSScene); -Q_DECLARE_METATYPE(OBSSceneItem); -Q_DECLARE_METATYPE(OBSSource); -Q_DECLARE_METATYPE(obs_order_movement); -Q_DECLARE_METATYPE(SignalContainer); - -QDataStream &operator<<(QDataStream &out, const SignalContainer &v) -{ - out << v.ref; - return out; -} - -QDataStream &operator>>(QDataStream &in, SignalContainer &v) -{ - in >> v.ref; - return in; -} - -template static T GetOBSRef(QListWidgetItem *item) -{ - return item->data(static_cast(QtDataRole::OBSRef)).value(); -} - -template static void SetOBSRef(QListWidgetItem *item, T &&val) -{ - item->setData(static_cast(QtDataRole::OBSRef), QVariant::fromValue(val)); -} - -static void AddExtraModulePaths() -{ - string plugins_path, plugins_data_path; - char *s; - - s = getenv("OBS_PLUGINS_PATH"); - if (s) - plugins_path = s; - - s = getenv("OBS_PLUGINS_DATA_PATH"); - if (s) - plugins_data_path = s; - - if (!plugins_path.empty() && !plugins_data_path.empty()) { -#if defined(__APPLE__) - plugins_path += "/%module%.plugin/Contents/MacOS"; - plugins_data_path += "/%module%.plugin/Contents/Resources"; - obs_add_module_path(plugins_path.c_str(), plugins_data_path.c_str()); -#else - string data_path_with_module_suffix; - data_path_with_module_suffix += plugins_data_path; - data_path_with_module_suffix += "/%module%"; - obs_add_module_path(plugins_path.c_str(), data_path_with_module_suffix.c_str()); -#endif - } - - if (portable_mode) - return; - - char base_module_dir[512]; -#if defined(_WIN32) - int ret = GetProgramDataPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#elif defined(__APPLE__) - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%.plugin"); -#else - int ret = GetAppConfigPath(base_module_dir, sizeof(base_module_dir), "obs-studio/plugins/%module%"); -#endif - - if (ret <= 0) - return; - - string path = base_module_dir; -#if defined(__APPLE__) - /* User Application Support Search Path */ - obs_add_module_path((path + "/Contents/MacOS").c_str(), (path + "/Contents/Resources").c_str()); - -#ifndef __aarch64__ - /* Legacy System Library Search Path */ - char system_legacy_module_dir[PATH_MAX]; - GetProgramDataPath(system_legacy_module_dir, sizeof(system_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_system_legacy = system_legacy_module_dir; - obs_add_module_path((path_system_legacy + "/bin").c_str(), (path_system_legacy + "/data").c_str()); - - /* Legacy User Application Support Search Path */ - char user_legacy_module_dir[PATH_MAX]; - GetAppConfigPath(user_legacy_module_dir, sizeof(user_legacy_module_dir), "obs-studio/plugins/%module%"); - std::string path_user_legacy = user_legacy_module_dir; - obs_add_module_path((path_user_legacy + "/bin").c_str(), (path_user_legacy + "/data").c_str()); -#endif -#else -#if ARCH_BITS == 64 - obs_add_module_path((path + "/bin/64bit").c_str(), (path + "/data").c_str()); -#else - obs_add_module_path((path + "/bin/32bit").c_str(), (path + "/data").c_str()); -#endif -#endif -} - -/* First-party modules considered to be potentially unsafe to load in Safe Mode - * due to them allowing external code (e.g. scripts) to modify OBS's state. */ -static const unordered_set unsafe_modules = { - "frontend-tools", // Scripting - "obs-websocket", // Allows outside modifications -}; - -static void SetSafeModuleNames() -{ -#ifndef SAFE_MODULES - return; -#else - string module; - stringstream modules(SAFE_MODULES); - - while (getline(modules, module, '|')) { - /* When only disallowing third-party plugins, still add - * "unsafe" bundled modules to the safe list. */ - if (disable_3p_plugins || !unsafe_modules.count(module)) - obs_add_safe_module(module.c_str()); - } -#endif -} - -extern obs_frontend_callbacks *InitializeAPIInterface(OBSBasic *main); - -void assignDockToggle(QDockWidget *dock, QAction *action) -{ - auto handleWindowToggle = [action](bool vis) { - action->blockSignals(true); - action->setChecked(vis); - action->blockSignals(false); - }; - auto handleMenuToggle = [dock](bool check) { - dock->blockSignals(true); - dock->setVisible(check); - dock->blockSignals(false); - }; - - dock->connect(dock->toggleViewAction(), &QAction::toggled, handleWindowToggle); - dock->connect(action, &QAction::toggled, handleMenuToggle); -} - -void setupDockAction(QDockWidget *dock) -{ - QAction *action = dock->toggleViewAction(); - - auto neverDisable = [action]() { - QSignalBlocker block(action); - action->setEnabled(true); - }; - - auto newToggleView = [dock](bool check) { - QSignalBlocker block(dock); - dock->setVisible(check); - }; - - // Replace the slot connected by default - QObject::disconnect(action, &QAction::triggered, nullptr, 0); - dock->connect(action, &QAction::triggered, newToggleView); - - // Make the action unable to be disabled - action->connect(action, &QAction::enabledChanged, neverDisable); -} - -extern void RegisterTwitchAuth(); -extern void RegisterRestreamAuth(); -#ifdef YOUTUBE_ENABLED -extern void RegisterYoutubeAuth(); -#endif - -OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) -{ - setAttribute(Qt::WA_NativeWindow); - -#ifdef TWITCH_ENABLED - RegisterTwitchAuth(); -#endif -#ifdef RESTREAM_ENABLED - RegisterRestreamAuth(); -#endif -#ifdef YOUTUBE_ENABLED - RegisterYoutubeAuth(); -#endif - - setAcceptDrops(true); - - setContextMenuPolicy(Qt::CustomContextMenu); - - QEvent::registerEventType(QEvent::User + QEvent::Close); - - api = InitializeAPIInterface(this); - - ui->setupUi(this); - ui->previewDisabledWidget->setVisible(false); - - /* Set up streaming connections */ - connect( - this, &OBSBasic::StreamingStarting, this, [this] { this->streamingStarting = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStarted, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::StreamingStopped, this, [this] { this->streamingStarting = false; }, - Qt::DirectConnection); - - /* Set up recording connections */ - connect( - this, &OBSBasic::RecordingStarted, this, - [this]() { - this->recordingStarted = true; - this->recordingPaused = false; - }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingPaused, this, [this]() { this->recordingPaused = true; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingUnpaused, this, [this]() { this->recordingPaused = false; }, - Qt::DirectConnection); - connect( - this, &OBSBasic::RecordingStopped, this, - [this]() { - this->recordingStarted = false; - this->recordingPaused = false; - }, - Qt::DirectConnection); - - /* Add controls dock */ - OBSBasicControls *controls = new OBSBasicControls(this); - controlsDock = new OBSDock(this); - controlsDock->setObjectName(QString::fromUtf8("controlsDock")); - controlsDock->setWindowTitle(QTStr("Basic.Main.Controls")); - /* Parenting is done there so controls will be deleted alongside controlsDock */ - controlsDock->setWidget(controls); - addDockWidget(Qt::BottomDockWidgetArea, controlsDock); - - connect(controls, &OBSBasicControls::StreamButtonClicked, this, &OBSBasic::StreamActionTriggered); - - connect(controls, &OBSBasicControls::StartStreamMenuActionClicked, this, &OBSBasic::StartStreaming); - connect(controls, &OBSBasicControls::StopStreamMenuActionClicked, this, &OBSBasic::StopStreaming); - connect(controls, &OBSBasicControls::ForceStopStreamMenuActionClicked, this, &OBSBasic::ForceStopStreaming); - - connect(controls, &OBSBasicControls::BroadcastButtonClicked, this, &OBSBasic::BroadcastButtonClicked); - - connect(controls, &OBSBasicControls::RecordButtonClicked, this, &OBSBasic::RecordActionTriggered); - connect(controls, &OBSBasicControls::PauseRecordButtonClicked, this, &OBSBasic::RecordPauseToggled); - - connect(controls, &OBSBasicControls::ReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferActionTriggered); - connect(controls, &OBSBasicControls::SaveReplayBufferButtonClicked, this, &OBSBasic::ReplayBufferSave); - - connect(controls, &OBSBasicControls::VirtualCamButtonClicked, this, &OBSBasic::VirtualCamActionTriggered); - connect(controls, &OBSBasicControls::VirtualCamConfigButtonClicked, this, &OBSBasic::OpenVirtualCamConfig); - - connect(controls, &OBSBasicControls::StudioModeButtonClicked, this, &OBSBasic::TogglePreviewProgramMode); - - connect(controls, &OBSBasicControls::SettingsButtonClicked, this, &OBSBasic::on_action_Settings_triggered); - - connect(controls, &OBSBasicControls::ExitButtonClicked, this, &QMainWindow::close); - - startingDockLayout = saveState(); - - statsDock = new OBSDock(); - statsDock->setObjectName(QStringLiteral("statsDock")); - statsDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - statsDock->setWindowTitle(QTStr("Basic.Stats")); - addDockWidget(Qt::BottomDockWidgetArea, statsDock); - statsDock->setVisible(false); - statsDock->setFloating(true); - statsDock->resize(700, 200); - - copyActionsDynamicProperties(); - - qRegisterMetaType("int64_t"); - qRegisterMetaType("uint32_t"); - qRegisterMetaType("OBSScene"); - qRegisterMetaType("OBSSceneItem"); - qRegisterMetaType("OBSSource"); - qRegisterMetaType("obs_hotkey_id"); - qRegisterMetaType("SavedProjectorInfo *"); - - ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); - ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); - - bool sceneGrid = config_get_bool(App()->GetUserConfig(), "BasicWindow", "gridMode"); - ui->scenes->SetGridMode(sceneGrid); - - if (sceneGrid) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); - - auto displayResize = [this]() { - struct obs_video_info ovi; - - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - - UpdateContextBarVisibility(); - UpdatePreviewScrollbars(); - dpi = devicePixelRatioF(); - }; - dpi = devicePixelRatioF(); - - connect(windowHandle(), &QWindow::screenChanged, displayResize); - connect(ui->preview, &OBSQTDisplay::DisplayResized, displayResize); - - /* TODO: Move these into window-basic-preview */ - /* Preview Scaling label */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalePercent, - &OBSPreviewScalingLabel::PreviewScaleChanged); - - /* Preview Scaling dropdown */ - connect(ui->preview, &OBSBasicPreview::scalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewScaleChanged); - - connect(ui->preview, &OBSBasicPreview::fixedScalingChanged, ui->previewScalingMode, - &OBSPreviewScalingComboBox::PreviewFixedScalingChanged); - - connect(ui->previewScalingMode, &OBSPreviewScalingComboBox::currentIndexChanged, this, - &OBSBasic::PreviewScalingModeChanged); - - connect(this, &OBSBasic::CanvasResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::CanvasResized); - connect(this, &OBSBasic::OutputResized, ui->previewScalingMode, &OBSPreviewScalingComboBox::OutputResized); - - delete shortcutFilter; - shortcutFilter = CreateShortcutFilter(); - installEventFilter(shortcutFilter); - - stringstream name; - name << "OBS " << App()->GetVersionString(); - blog(LOG_INFO, "%s", name.str().c_str()); - blog(LOG_INFO, "---------------------------------"); - - UpdateTitleBar(); - - connect(ui->scenes->itemDelegate(), &QAbstractItemDelegate::closeEditor, this, &OBSBasic::SceneNameEdited); - - cpuUsageInfo = os_cpu_usage_info_start(); - cpuUsageTimer = new QTimer(this); - connect(cpuUsageTimer.data(), &QTimer::timeout, ui->statusbar, &OBSBasicStatusBar::UpdateCPUUsage); - cpuUsageTimer->start(3000); - - diskFullTimer = new QTimer(this); - connect(diskFullTimer, &QTimer::timeout, this, &OBSBasic::CheckDiskSpaceRemaining); - - renameScene = new QAction(QTStr("Rename"), ui->scenesDock); - renameScene->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameScene, &QAction::triggered, this, &OBSBasic::EditSceneName); - ui->scenesDock->addAction(renameScene); - - renameSource = new QAction(QTStr("Rename"), ui->sourcesDock); - renameSource->setShortcutContext(Qt::WidgetWithChildrenShortcut); - connect(renameSource, &QAction::triggered, this, &OBSBasic::EditSceneItemName); - ui->sourcesDock->addAction(renameSource); - -#ifdef __APPLE__ - renameScene->setShortcut({Qt::Key_Return}); - renameSource->setShortcut({Qt::Key_Return}); - - ui->actionRemoveSource->setShortcuts({Qt::Key_Backspace}); - ui->actionRemoveScene->setShortcuts({Qt::Key_Backspace}); - - ui->actionCheckForUpdates->setMenuRole(QAction::AboutQtRole); - ui->action_Settings->setMenuRole(QAction::PreferencesRole); - ui->actionShowMacPermissions->setMenuRole(QAction::ApplicationSpecificRole); - ui->actionE_xit->setMenuRole(QAction::QuitRole); -#else - renameScene->setShortcut({Qt::Key_F2}); - renameSource->setShortcut({Qt::Key_F2}); -#endif - -#ifdef __linux__ - ui->actionE_xit->setShortcut(Qt::CTRL | Qt::Key_Q); -#endif - - auto addNudge = [this](const QKeySequence &seq, MoveDir direction, int distance) { - QAction *nudge = new QAction(ui->preview); - nudge->setShortcut(seq); - nudge->setShortcutContext(Qt::WidgetShortcut); - ui->preview->addAction(nudge); - connect(nudge, &QAction::triggered, [this, distance, direction]() { Nudge(distance, direction); }); - }; - - addNudge(Qt::Key_Up, MoveDir::Up, 1); - addNudge(Qt::Key_Down, MoveDir::Down, 1); - addNudge(Qt::Key_Left, MoveDir::Left, 1); - addNudge(Qt::Key_Right, MoveDir::Right, 1); - addNudge(Qt::SHIFT | Qt::Key_Up, MoveDir::Up, 10); - addNudge(Qt::SHIFT | Qt::Key_Down, MoveDir::Down, 10); - addNudge(Qt::SHIFT | Qt::Key_Left, MoveDir::Left, 10); - addNudge(Qt::SHIFT | Qt::Key_Right, MoveDir::Right, 10); - - /* Setup dock toggle action - * And hide all docks before restoring parent geometry */ -#define SETUP_DOCK(dock) \ - setupDockAction(dock); \ - ui->menuDocks->addAction(dock->toggleViewAction()); \ - dock->setVisible(false); - - SETUP_DOCK(ui->scenesDock); - SETUP_DOCK(ui->sourcesDock); - SETUP_DOCK(ui->mixerDock); - SETUP_DOCK(ui->transitionsDock); - SETUP_DOCK(controlsDock); - SETUP_DOCK(statsDock); -#undef SETUP_DOCK - - // Register shortcuts for Undo/Redo - ui->actionMainUndo->setShortcut(Qt::CTRL | Qt::Key_Z); - QList shrt; - shrt << QKeySequence((Qt::CTRL | Qt::SHIFT) | Qt::Key_Z) << QKeySequence(Qt::CTRL | Qt::Key_Y); - ui->actionMainRedo->setShortcuts(shrt); - - ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); - ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); - - QPoint curPos; - - //restore parent window geometry - const char *geometry = config_get_string(App()->GetUserConfig(), "BasicWindow", "geometry"); - if (geometry != NULL) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(geometry)); - restoreGeometry(byteArray); - - QRect windowGeometry = normalGeometry(); - if (!WindowPositionValid(windowGeometry)) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - curPos = pos(); - } else { - QRect desktopRect = QGuiApplication::primaryScreen()->geometry(); - QSize adjSize = desktopRect.size() / 2 - size() / 2; - curPos = QPoint(adjSize.width(), adjSize.height()); - } - - QPoint curSize(width(), height()); - - QPoint statsDockSize(statsDock->width(), statsDock->height()); - QPoint statsDockPos = curSize / 2 - statsDockSize / 2; - QPoint newPos = curPos + statsDockPos; - statsDock->move(newPos); - -#ifdef HAVE_OBSCONFIG_H - ui->actionReleaseNotes->setVisible(true); -#endif - - ui->previewDisabledWidget->setContextMenuPolicy(Qt::CustomContextMenu); - connect(ui->enablePreviewButton, &QPushButton::clicked, this, &OBSBasic::TogglePreview); - - connect(ui->scenes, &SceneTree::scenesReordered, []() { OBSProjector::UpdateMultiviewProjectors(); }); - - connect(App(), &OBSApp::StyleChanged, this, [this]() { OnEvent(OBS_FRONTEND_EVENT_THEME_CHANGED); }); - - QActionGroup *actionGroup = new QActionGroup(this); - actionGroup->addAction(ui->actionSceneListMode); - actionGroup->addAction(ui->actionSceneGridMode); - - UpdatePreviewSafeAreas(); - UpdatePreviewSpacingHelpers(); - UpdatePreviewOverflowSettings(); -} - -static void SaveAudioDevice(const char *name, int channel, obs_data_t *parent, vector &audioSources) -{ - OBSSourceAutoRelease source = obs_get_output_source(channel); - if (!source) - return; - - audioSources.push_back(source.Get()); - - OBSDataAutoRelease data = obs_save_source(source); - - obs_data_set_obj(parent, name, data); -} - -static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_array_t *quickTransitionData, - int transitionDuration, obs_data_array_t *transitions, OBSScene &scene, - OBSSource &curProgramScene, obs_data_array_t *savedProjectorList) -{ - obs_data_t *saveData = obs_data_create(); - - vector audioSources; - audioSources.reserve(6); - - SaveAudioDevice(DESKTOP_AUDIO_1, 1, saveData, audioSources); - SaveAudioDevice(DESKTOP_AUDIO_2, 2, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_1, 3, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); - SaveAudioDevice(AUX_AUDIO_4, 6, saveData, audioSources); - - /* -------------------------------- */ - /* save non-group sources */ - - auto FilterAudioSources = [&](obs_source_t *source) { - if (obs_source_is_group(source)) - return false; - - return find(begin(audioSources), end(audioSources), source) == end(audioSources); - }; - using FilterAudioSources_t = decltype(FilterAudioSources); - - obs_data_array_t *sourcesArray = obs_save_sources_filtered( - [](void *data, obs_source_t *source) { - auto &func = *static_cast(data); - return func(source); - }, - static_cast(&FilterAudioSources)); - - /* -------------------------------- */ - /* save group sources separately */ - - /* saving separately ensures they won't be loaded in older versions */ - obs_data_array_t *groupsArray = obs_save_sources_filtered( - [](void *, obs_source_t *source) { return obs_source_is_group(source); }, nullptr); - - /* -------------------------------- */ - - OBSSourceAutoRelease transition = obs_get_output_source(0); - obs_source_t *currentScene = obs_scene_get_source(scene); - const char *sceneName = obs_source_get_name(currentScene); - const char *programName = obs_source_get_name(curProgramScene); - - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_string(saveData, "current_scene", sceneName); - obs_data_set_string(saveData, "current_program_scene", programName); - obs_data_set_array(saveData, "scene_order", sceneOrder); - obs_data_set_string(saveData, "name", sceneCollection); - obs_data_set_array(saveData, "sources", sourcesArray); - obs_data_set_array(saveData, "groups", groupsArray); - obs_data_set_array(saveData, "quick_transitions", quickTransitionData); - obs_data_set_array(saveData, "transitions", transitions); - obs_data_set_array(saveData, "saved_projectors", savedProjectorList); - obs_data_array_release(sourcesArray); - obs_data_array_release(groupsArray); - - obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); - obs_data_set_int(saveData, "transition_duration", transitionDuration); - - return saveData; -} - -void OBSBasic::copyActionsDynamicProperties() -{ - // Themes need the QAction dynamic properties - for (QAction *x : ui->scenesToolbar->actions()) { - QWidget *temp = ui->scenesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->sourcesToolbar->actions()) { - QWidget *temp = ui->sourcesToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } - - for (QAction *x : ui->mixerToolbar->actions()) { - QWidget *temp = ui->mixerToolbar->widgetForAction(x); - - if (!temp) - continue; - - for (QByteArray &y : x->dynamicPropertyNames()) { - temp->setProperty(y, x->property(y)); - } - } -} - -void OBSBasic::UpdateVolumeControlsDecayRate() -{ - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->SetMeterDecayRate(meterDecayRate); - } -} - -void OBSBasic::UpdateVolumeControlsPeakMeterType() -{ - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - for (size_t i = 0; i < volumes.size(); i++) { - volumes[i]->setPeakMeterType(peakMeterType); - } -} - -void OBSBasic::ClearVolumeControls() -{ - for (VolControl *vol : volumes) - delete vol; - - volumes.clear(); -} - -void OBSBasic::RefreshVolumeColors() -{ - for (VolControl *vol : volumes) { - vol->refreshColors(); - } -} - -obs_data_array_t *OBSBasic::SaveSceneListOrder() -{ - obs_data_array_t *sceneOrder = obs_data_array_create(); - - for (int i = 0; i < ui->scenes->count(); i++) { - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", QT_TO_UTF8(ui->scenes->item(i)->text())); - obs_data_array_push_back(sceneOrder, data); - } - - return sceneOrder; -} - -obs_data_array_t *OBSBasic::SaveProjectors() -{ - obs_data_array_t *savedProjectors = obs_data_array_create(); - - auto saveProjector = [savedProjectors](OBSProjector *projector) { - if (!projector) - return; - - OBSDataAutoRelease data = obs_data_create(); - ProjectorType type = projector->GetProjectorType(); - - switch (type) { - case ProjectorType::Scene: - case ProjectorType::Source: { - OBSSource source = projector->GetSource(); - const char *name = obs_source_get_name(source); - obs_data_set_string(data, "name", name); - break; - } - default: - break; - } - - obs_data_set_int(data, "monitor", projector->GetMonitor()); - obs_data_set_int(data, "type", static_cast(type)); - obs_data_set_string(data, "geometry", projector->saveGeometry().toBase64().constData()); - - if (projector->IsAlwaysOnTopOverridden()) - obs_data_set_bool(data, "alwaysOnTop", projector->IsAlwaysOnTop()); - - obs_data_set_bool(data, "alwaysOnTopOverridden", projector->IsAlwaysOnTopOverridden()); - - obs_data_array_push_back(savedProjectors, data); - }; - - for (size_t i = 0; i < projectors.size(); i++) - saveProjector(static_cast(projectors[i])); - - return savedProjectors; -} - -void OBSBasic::Save(const char *file) -{ - OBSScene scene = GetCurrentScene(); - OBSSource curProgramScene = OBSGetStrongRef(programScene); - if (!curProgramScene) - curProgramScene = obs_scene_get_source(scene); - - OBSDataArrayAutoRelease sceneOrder = SaveSceneListOrder(); - OBSDataArrayAutoRelease transitions = SaveTransitions(); - OBSDataArrayAutoRelease quickTrData = SaveQuickTransitions(); - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - OBSDataAutoRelease saveData = GenerateSaveData(sceneOrder, quickTrData, ui->transitionDuration->value(), - transitions, scene, curProgramScene, savedProjectorList); - - obs_data_set_bool(saveData, "preview_locked", ui->preview->Locked()); - obs_data_set_bool(saveData, "scaling_enabled", ui->preview->IsFixedScaling()); - obs_data_set_int(saveData, "scaling_level", ui->preview->GetScalingLevel()); - obs_data_set_double(saveData, "scaling_off_x", ui->preview->GetScrollX()); - obs_data_set_double(saveData, "scaling_off_y", ui->preview->GetScrollY()); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_create(); - - obs_data_set_int(obj, "type2", (int)vcamConfig.type); - switch (vcamConfig.type) { - case VCamOutputType::Invalid: - case VCamOutputType::ProgramView: - case VCamOutputType::PreviewOutput: - break; - case VCamOutputType::SceneOutput: - obs_data_set_string(obj, "scene", vcamConfig.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - obs_data_set_string(obj, "source", vcamConfig.source.c_str()); - break; - } - - obs_data_set_obj(saveData, "virtual-camera", obj); - } - - if (api) { - if (!collectionModuleData) - collectionModuleData = obs_data_create(); - - api->on_save(collectionModuleData); - obs_data_set_obj(saveData, "modules", collectionModuleData); - } - - if (lastOutputResolution) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", lastOutputResolution->first); - obs_data_set_int(res, "y", lastOutputResolution->second); - obs_data_set_obj(saveData, "resolution", res); - } - - obs_data_set_int(saveData, "version", usingAbsoluteCoordinates ? 1 : 2); - - if (migrationBaseResolution && !usingAbsoluteCoordinates) { - OBSDataAutoRelease res = obs_data_create(); - obs_data_set_int(res, "x", migrationBaseResolution->first); - obs_data_set_int(res, "y", migrationBaseResolution->second); - obs_data_set_obj(saveData, "migration_resolution", res); - } - - if (!obs_data_save_json_pretty_safe(saveData, file, "tmp", "bak")) - blog(LOG_ERROR, "Could not save scene data to %s", file); -} - -void OBSBasic::DeferSaveBegin() -{ - os_atomic_inc_long(&disableSaving); -} - -void OBSBasic::DeferSaveEnd() -{ - long result = os_atomic_dec_long(&disableSaving); - if (result == 0) { - SaveProject(); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val); - -static void LoadAudioDevice(const char *name, int channel, obs_data_t *parent) -{ - OBSDataAutoRelease data = obs_data_get_obj(parent, name); - if (!data) - return; - - OBSSourceAutoRelease source = obs_load_source(data); - if (!source) - return; - - obs_set_output_source(channel, source); - - const char *source_name = obs_source_get_name(source); - blog(LOG_INFO, "[Loaded global audio device]: '%s'", source_name); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " - monitoring: %s", type); - } -} - -static inline bool HasAudioDevices(const char *source_id) -{ - const char *output_id = source_id; - obs_properties_t *props = obs_get_source_properties(output_id); - size_t count = 0; - - if (!props) - return false; - - obs_property_t *devices = obs_properties_get(props, "device_id"); - if (devices) - count = obs_property_list_item_count(devices); - - obs_properties_destroy(props); - - return count != 0; -} - -void OBSBasic::CreateFirstRunSources() -{ - bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource()); - bool hasInputAudio = HasAudioDevices(App()->InputAudioSource()); - -#ifdef __APPLE__ - /* On macOS 13 and above, the SCK based audio capture provides a - * better alternative to the device-based audio capture. */ - if (__builtin_available(macOS 13.0, *)) { - hasDesktopAudio = false; - } -#endif - - if (hasDesktopAudio) - ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1); - if (hasInputAudio) - ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3); -} - -void OBSBasic::DisableRelativeCoordinates(bool enable) -{ - /* Allow disabling relative positioning to allow loading collections - * that cannot yet be migrated. */ - OBSDataAutoRelease priv = obs_get_private_data(); - obs_data_set_bool(priv, "AbsoluteCoordinates", enable); - usingAbsoluteCoordinates = enable; - - ui->actionRemigrateSceneCollection->setText(enable ? QTStr("Basic.MainMenu.SceneCollection.Migrate") - : QTStr("Basic.MainMenu.SceneCollection.Remigrate")); - ui->actionRemigrateSceneCollection->setEnabled(enable); -} - -void OBSBasic::CreateDefaultScene(bool firstStart) -{ - disableSaving++; - - ClearSceneData(); - InitDefaultTransitions(); - CreateDefaultQuickTransitions(); - ui->transitionDuration->setValue(300); - SetTransition(fadeTransition); - - DisableRelativeCoordinates(false); - OBSSceneAutoRelease scene = obs_scene_create(Str("Basic.Scene")); - - if (firstStart) - CreateFirstRunSources(); - - SetCurrentScene(scene, true); - - disableSaving--; -} - -static void ReorderItemByName(QListWidget *lw, const char *name, int newIndex) -{ - for (int i = 0; i < lw->count(); i++) { - QListWidgetItem *item = lw->item(i); - - if (strcmp(name, QT_TO_UTF8(item->text())) == 0) { - if (newIndex != i) { - item = lw->takeItem(i); - lw->insertItem(newIndex, item); - } - break; - } - } -} - -void OBSBasic::LoadSceneListOrder(obs_data_array_t *array) -{ - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - ReorderItemByName(ui->scenes, name, (int)i); - } -} - -void OBSBasic::LoadSavedProjectors(obs_data_array_t *array) -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - delete info; - } - savedProjectorsArray.clear(); - - size_t num = obs_data_array_count(array); - - for (size_t i = 0; i < num; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - - SavedProjectorInfo *info = new SavedProjectorInfo(); - info->monitor = obs_data_get_int(data, "monitor"); - info->type = static_cast(obs_data_get_int(data, "type")); - info->geometry = std::string(obs_data_get_string(data, "geometry")); - info->name = std::string(obs_data_get_string(data, "name")); - info->alwaysOnTop = obs_data_get_bool(data, "alwaysOnTop"); - info->alwaysOnTopOverridden = obs_data_get_bool(data, "alwaysOnTopOverridden"); - - savedProjectorsArray.emplace_back(info); - } -} - -static void LogFilter(obs_source_t *, obs_source_t *filter, void *v_val) -{ - const char *name = obs_source_get_name(filter); - const char *id = obs_source_get_id(filter); - int val = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < val; i++) - indent += " "; - - blog(LOG_INFO, "%s- filter: '%s' (%s)", indent.c_str(), name, id); -} - -static bool LogSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *v_val) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - const char *name = obs_source_get_name(source); - const char *id = obs_source_get_id(source); - int indent_count = (int)(intptr_t)v_val; - string indent; - - for (int i = 0; i < indent_count; i++) - indent += " "; - - blog(LOG_INFO, "%s- source: '%s' (%s)", indent.c_str(), name, id); - - obs_monitoring_type monitoring_type = obs_source_get_monitoring_type(source); - - if (monitoring_type != OBS_MONITORING_TYPE_NONE) { - const char *type = (monitoring_type == OBS_MONITORING_TYPE_MONITOR_ONLY) ? "monitor only" - : "monitor and output"; - - blog(LOG_INFO, " %s- monitoring: %s", indent.c_str(), type); - } - int child_indent = 1 + indent_count; - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)child_indent); - - obs_source_t *show_tn = obs_sceneitem_get_transition(item, true); - obs_source_t *hide_tn = obs_sceneitem_get_transition(item, false); - if (show_tn) - blog(LOG_INFO, " %s- show: '%s' (%s)", indent.c_str(), obs_source_get_name(show_tn), - obs_source_get_id(show_tn)); - if (hide_tn) - blog(LOG_INFO, " %s- hide: '%s' (%s)", indent.c_str(), obs_source_get_name(hide_tn), - obs_source_get_id(hide_tn)); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, LogSceneItem, (void *)(intptr_t)child_indent); - return true; -} - -void OBSBasic::LogScenes() -{ - blog(LOG_INFO, "------------------------------------------------"); - blog(LOG_INFO, "Loaded scenes:"); - - for (int i = 0; i < ui->scenes->count(); i++) { - QListWidgetItem *item = ui->scenes->item(i); - OBSScene scene = GetOBSRef(item); - - obs_source_t *source = obs_scene_get_source(scene); - const char *name = obs_source_get_name(source); - - blog(LOG_INFO, "- scene '%s':", name); - obs_scene_enum_items(scene, LogSceneItem, (void *)(intptr_t)1); - obs_source_enum_filters(source, LogFilter, (void *)(intptr_t)1); - } - - blog(LOG_INFO, "------------------------------------------------"); -} - -void OBSBasic::Load(const char *file, bool remigrate) -{ - disableSaving++; - lastOutputResolution.reset(); - migrationBaseResolution.reset(); - - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); - if (!data) { - disableSaving--; - const auto path = filesystem::u8path(file); - const string name = path.stem().u8string(); - /* Check if file exists but failed to load. */ - if (filesystem::exists(path)) { - /* Assume the file is corrupt and rename it to allow - * for manual recovery if possible. */ - auto newPath = path; - newPath.concat(".invalid"); - - blog(LOG_WARNING, - "File exists but appears to be corrupt, renaming " - "to \"%s\" before continuing.", - newPath.filename().u8string().c_str()); - - error_code ec; - filesystem::rename(path, newPath, ec); - if (ec) { - blog(LOG_ERROR, "Failed renaming corrupt file with %d", ec.value()); - } - } - - blog(LOG_INFO, "No scene file found, creating default scene"); - - bool hasFirstRun = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - CreateDefaultScene(!hasFirstRun); - SaveProject(); - return; - } - - LoadData(data, file, remigrate); -} - -static inline void AddMissingFiles(void *data, obs_source_t *source) -{ - obs_missing_files_t *f = (obs_missing_files_t *)data; - obs_missing_files_t *sf = obs_source_get_missing_files(source); - - obs_missing_files_append(f, sf); - obs_missing_files_destroy(sf); -} - -static void ClearRelativePosCb(obs_data_t *data, void *) -{ - const string_view id = obs_data_get_string(data, "id"); - if (id != "scene" && id != "group") - return; - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - obs_data_array_enum( - items, - [](obs_data_t *data, void *) { - obs_data_unset_user_value(data, "pos_rel"); - obs_data_unset_user_value(data, "scale_rel"); - obs_data_unset_user_value(data, "scale_ref"); - obs_data_unset_user_value(data, "bounds_rel"); - }, - nullptr); -} - -void OBSBasic::LoadData(obs_data_t *data, const char *file, bool remigrate) -{ - ClearSceneData(); - ClearContextBar(); - - /* Exit OBS if clearing scene data failed for some reason. */ - if (clearingFailed) { - OBSMessageBox::critical(this, QTStr("SourceLeak.Title"), QTStr("SourceLeak.Text")); - close(); - return; - } - - InitDefaultTransitions(); - - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - OBSDataAutoRelease modulesObj = obs_data_get_obj(data, "modules"); - if (api) - api->on_preload(modulesObj); - - /* Keep a reference to "modules" data so plugins that are not loaded do - * not have their collection specific data lost. */ - collectionModuleData = obs_data_get_obj(data, "modules"); - - OBSDataArrayAutoRelease sceneOrder = obs_data_get_array(data, "scene_order"); - OBSDataArrayAutoRelease sources = obs_data_get_array(data, "sources"); - OBSDataArrayAutoRelease groups = obs_data_get_array(data, "groups"); - OBSDataArrayAutoRelease transitions = obs_data_get_array(data, "transitions"); - const char *sceneName = obs_data_get_string(data, "current_scene"); - const char *programSceneName = obs_data_get_string(data, "current_program_scene"); - const char *transitionName = obs_data_get_string(data, "current_transition"); - - if (!opt_starting_scene.empty()) { - programSceneName = opt_starting_scene.c_str(); - if (!IsPreviewProgramMode()) - sceneName = opt_starting_scene.c_str(); - } - - int newDuration = obs_data_get_int(data, "transition_duration"); - if (!newDuration) - newDuration = 300; - - if (!transitionName) - transitionName = obs_source_get_name(fadeTransition); - - const char *curSceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - obs_data_set_default_string(data, "name", curSceneCollection); - - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease curScene; - OBSSourceAutoRelease curProgramScene; - obs_source_t *curTransition; - - if (!name || !*name) - name = curSceneCollection; - - LoadAudioDevice(DESKTOP_AUDIO_1, 1, data); - LoadAudioDevice(DESKTOP_AUDIO_2, 2, data); - LoadAudioDevice(AUX_AUDIO_1, 3, data); - LoadAudioDevice(AUX_AUDIO_2, 4, data); - LoadAudioDevice(AUX_AUDIO_3, 5, data); - LoadAudioDevice(AUX_AUDIO_4, 6, data); - - if (!sources) { - sources = std::move(groups); - } else { - obs_data_array_push_back_array(sources, groups); - } - - /* Reset relative coordinate data if forcefully remigrating. */ - if (remigrate) { - obs_data_set_int(data, "version", 1); - obs_data_array_enum(sources, ClearRelativePosCb, nullptr); - } - - bool resetVideo = false; - bool disableRelativeCoords = false; - obs_video_info ovi; - - int64_t version = obs_data_get_int(data, "version"); - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (res) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - - /* Only migrate legacy collection if resolution is saved. */ - if (version < 2 && lastOutputResolution) { - obs_get_video_info(&ovi); - - uint32_t width = obs_data_get_int(res, "x"); - uint32_t height = obs_data_get_int(res, "y"); - - migrationBaseResolution = {width, height}; - - if (ovi.base_height != height || ovi.base_width != width) { - ovi.base_width = width; - ovi.base_height = height; - - /* Attempt to reset to last known canvas resolution for migration. */ - resetVideo = obs_reset_video(&ovi) == OBS_VIDEO_SUCCESS; - disableRelativeCoords = !resetVideo; - } - - /* If migration is possible, and it wasn't forced, back up the original file. */ - if (!disableRelativeCoords && !remigrate) { - auto path = filesystem::u8path(file); - auto backupPath = path.concat(".v1"); - if (!filesystem::exists(backupPath)) { - if (!obs_data_save_json_pretty_safe(data, backupPath.u8string().c_str(), "tmp", NULL)) { - blog(LOG_WARNING, - "Failed to create a backup of existing scene collection data!"); - } - } - } - } else if (version < 2) { - disableRelativeCoords = true; - } else if (OBSDataAutoRelease migration_res = obs_data_get_obj(data, "migration_resolution")) { - migrationBaseResolution = {obs_data_get_int(migration_res, "x"), obs_data_get_int(migration_res, "y")}; - } - - DisableRelativeCoordinates(disableRelativeCoords); - - obs_missing_files_t *files = obs_missing_files_create(); - obs_load_sources(sources, AddMissingFiles, files); - - if (resetVideo) - ResetVideo(); - if (transitions) - LoadTransitions(transitions, AddMissingFiles, files); - if (sceneOrder) - LoadSceneListOrder(sceneOrder); - - curTransition = FindTransition(transitionName); - if (!curTransition) - curTransition = fadeTransition; - - ui->transitionDuration->setValue(newDuration); - SetTransition(curTransition); - -retryScene: - curScene = obs_get_source_by_name(sceneName); - curProgramScene = obs_get_source_by_name(programSceneName); - - /* if the starting scene command line parameter is bad at all, - * fall back to original settings */ - if (!opt_starting_scene.empty() && (!curScene || !curProgramScene)) { - sceneName = obs_data_get_string(data, "current_scene"); - programSceneName = obs_data_get_string(data, "current_program_scene"); - opt_starting_scene.clear(); - goto retryScene; - } - - if (!curScene) { - auto find_scene_cb = [](void *source_ptr, obs_source_t *scene) { - *static_cast(source_ptr) = obs_source_get_ref(scene); - return false; - }; - obs_enum_scenes(find_scene_cb, &curScene); - } - - SetCurrentScene(curScene.Get(), true); - - if (!curProgramScene) - curProgramScene = std::move(curScene); - if (IsPreviewProgramMode()) - TransitionToScene(curProgramScene.Get(), true); - - /* ------------------- */ - - bool projectorSave = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SaveProjectors"); - - if (projectorSave) { - OBSDataArrayAutoRelease savedProjectors = obs_data_get_array(data, "saved_projectors"); - - if (savedProjectors) { - LoadSavedProjectors(savedProjectors); - OpenSavedProjectors(); - activateWindow(); - } - } - - /* ------------------- */ - - std::string file_base = strrchr(file, '/') + 1; - file_base.erase(file_base.size() - 5, 5); - - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollection", name); - config_set_string(App()->GetUserConfig(), "Basic", "SceneCollectionFile", file_base.c_str()); - - OBSDataArrayAutoRelease quickTransitionData = obs_data_get_array(data, "quick_transitions"); - LoadQuickTransitions(quickTransitionData); - - RefreshQuickTransitions(); - - bool previewLocked = obs_data_get_bool(data, "preview_locked"); - ui->preview->SetLocked(previewLocked); - ui->actionLockPreview->setChecked(previewLocked); - - /* ---------------------- */ - - bool fixedScaling = obs_data_get_bool(data, "scaling_enabled"); - int scalingLevel = (int)obs_data_get_int(data, "scaling_level"); - float scrollOffX = (float)obs_data_get_double(data, "scaling_off_x"); - float scrollOffY = (float)obs_data_get_double(data, "scaling_off_y"); - - if (fixedScaling) { - ui->preview->SetScalingLevel(scalingLevel); - ui->preview->SetScrollingOffset(scrollOffX, scrollOffY); - } - ui->preview->SetFixedScaling(fixedScaling); - - emit ui->preview->DisplayResized(); - - if (vcamEnabled) { - OBSDataAutoRelease obj = obs_data_get_obj(data, "virtual-camera"); - - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type2"); - if (vcamConfig.type == VCamOutputType::Invalid) - vcamConfig.type = (VCamOutputType)obs_data_get_int(obj, "type"); - - if (vcamConfig.type == VCamOutputType::Invalid) { - VCamInternalType internal = (VCamInternalType)obs_data_get_int(obj, "internal"); - - switch (internal) { - case VCamInternalType::Default: - vcamConfig.type = VCamOutputType::ProgramView; - break; - case VCamInternalType::Preview: - vcamConfig.type = VCamOutputType::PreviewOutput; - break; - } - } - vcamConfig.scene = obs_data_get_string(obj, "scene"); - vcamConfig.source = obs_data_get_string(obj, "source"); - } - - if (obs_data_has_user_value(data, "resolution")) { - OBSDataAutoRelease res = obs_data_get_obj(data, "resolution"); - if (obs_data_has_user_value(res, "x") && obs_data_has_user_value(res, "y")) { - lastOutputResolution = {obs_data_get_int(res, "x"), obs_data_get_int(res, "y")}; - } - } - - /* ---------------------- */ - - if (api) - api->on_load(modulesObj); - - obs_data_release(data); - - if (!opt_starting_scene.empty()) - opt_starting_scene.clear(); - - if (opt_start_streaming && !safe_mode) { - blog(LOG_INFO, "Starting stream due to command line parameter"); - QMetaObject::invokeMethod(this, "StartStreaming", Qt::QueuedConnection); - opt_start_streaming = false; - } - - if (opt_start_recording && !safe_mode) { - blog(LOG_INFO, "Starting recording due to command line parameter"); - QMetaObject::invokeMethod(this, "StartRecording", Qt::QueuedConnection); - opt_start_recording = false; - } - - if (opt_start_replaybuffer && !safe_mode) { - QMetaObject::invokeMethod(this, "StartReplayBuffer", Qt::QueuedConnection); - opt_start_replaybuffer = false; - } - - if (opt_start_virtualcam && !safe_mode) { - QMetaObject::invokeMethod(this, "StartVirtualCam", Qt::QueuedConnection); - opt_start_virtualcam = false; - } - - LogScenes(); - - if (!App()->IsMissingFilesCheckDisabled()) - ShowMissingFilesDialog(files); - - disableSaving--; - - if (vcamEnabled) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); -} - -constexpr std::string_view OBSServiceFileName = "service.json"; - -void OBSBasic::SaveService() -{ - if (!service) - return; - - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - OBSDataAutoRelease data = obs_data_create(); - OBSDataAutoRelease settings = obs_service_get_settings(service); - - obs_data_set_string(data, "type", obs_service_get_type(service)); - obs_data_set_obj(data, "settings", settings); - - if (!obs_data_save_json_safe(data, jsonFilePath.u8string().c_str(), "tmp", "bak")) { - blog(LOG_WARNING, "Failed to save service"); - } -} - -bool OBSBasic::LoadService() -{ - OBSDataAutoRelease data; - - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - - const std::filesystem::path jsonFilePath = - currentProfile.path / std::filesystem::u8path(OBSServiceFileName); - - data = obs_data_create_from_json_file_safe(jsonFilePath.u8string().c_str(), "bak"); - - if (!data) { - return false; - } - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - return false; - } - - const char *type; - obs_data_set_default_string(data, "type", "rtmp_common"); - type = obs_data_get_string(data, "type"); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataAutoRelease hotkey_data = obs_data_get_obj(data, "hotkeys"); - - service = obs_service_create(type, "default_service", settings, hotkey_data); - obs_service_release(service); - - if (!service) - return false; - - /* Enforce Opus on WHIP if needed */ - if (strcmp(obs_service_get_protocol(service), "WHIP") == 0) { - const char *option = config_get_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder"); - if (strcmp(option, "opus") != 0) - config_set_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "opus"); - - option = config_get_string(activeConfiguration, "AdvOut", "AudioEncoder"); - - const char *encoder_codec = obs_get_encoder_codec(option); - if (!encoder_codec || strcmp(encoder_codec, "opus") != 0) - config_set_string(activeConfiguration, "AdvOut", "AudioEncoder", "ffmpeg_opus"); - } - - return true; -} - -bool OBSBasic::InitService() -{ - ProfileScope("OBSBasic::InitService"); - - if (LoadService()) - return true; - - service = obs_service_create("rtmp_common", "default_service", nullptr, nullptr); - if (!service) - return false; - obs_service_release(service); - - return true; -} - -static const double scaled_vals[] = {1.0, 1.25, (1.0 / 0.75), 1.5, (1.0 / 0.6), 1.75, 2.0, 2.25, 2.5, 2.75, 3.0, 0.0}; - -extern void CheckExistingCookieId(); - -#ifdef __APPLE__ -#define DEFAULT_CONTAINER "fragmented_mov" -#elif OBS_RELEASE_CANDIDATE == 0 && OBS_BETA == 0 -#define DEFAULT_CONTAINER "mkv" -#else -#define DEFAULT_CONTAINER "hybrid_mp4" -#endif - -bool OBSBasic::InitBasicConfigDefaults() -{ - QList screens = QGuiApplication::screens(); - - if (!screens.size()) { - OBSErrorBox(NULL, "There appears to be no monitors. Er, this " - "technically shouldn't be possible."); - return false; - } - - QScreen *primaryScreen = QGuiApplication::primaryScreen(); - - uint32_t cx = primaryScreen->size().width(); - uint32_t cy = primaryScreen->size().height(); - - cx *= devicePixelRatioF(); - cy *= devicePixelRatioF(); - - bool oldResolutionDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre19Defaults"); - - /* use 1920x1080 for new default base res if main monitor is above - * 1920x1080, but don't apply for people from older builds -- only to - * new users */ - if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) { - cx = 1920; - cy = 1080; - } - - bool changed = false; - - /* ----------------------------------------------------- */ - /* move over old FFmpeg track settings */ - if (config_has_user_value(activeConfiguration, "AdvOut", "FFAudioTrack") && - !config_has_user_value(activeConfiguration, "AdvOut", "Pre22.1Settings")) { - - int track = (int)config_get_int(activeConfiguration, "AdvOut", "FFAudioTrack"); - config_set_int(activeConfiguration, "AdvOut", "FFAudioMixes", 1LL << (track - 1)); - config_set_bool(activeConfiguration, "AdvOut", "Pre22.1Settings", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move over mixer values in advanced if older config */ - if (config_has_user_value(activeConfiguration, "AdvOut", "RecTrackIndex") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecTracks")) { - - uint64_t track = config_get_uint(activeConfiguration, "AdvOut", "RecTrackIndex"); - track = 1ULL << (track - 1); - config_set_uint(activeConfiguration, "AdvOut", "RecTracks", track); - config_remove_value(activeConfiguration, "AdvOut", "RecTrackIndex"); - changed = true; - } - - /* ----------------------------------------------------- */ - /* set twitch chat extensions to "both" if prev version */ - /* is under 24.1 */ - if (config_get_bool(App()->GetUserConfig(), "General", "Pre24.1Defaults") && - !config_has_user_value(activeConfiguration, "Twitch", "AddonChoice")) { - config_set_int(activeConfiguration, "Twitch", "AddonChoice", 3); - changed = true; - } - - /* ----------------------------------------------------- */ - /* move bitrate enforcement setting to new value */ - if (config_has_user_value(activeConfiguration, "SimpleOutput", "EnforceBitrate") && - !config_has_user_value(activeConfiguration, "Stream1", "IgnoreRecommended") && - !config_has_user_value(activeConfiguration, "Stream1", "MovedOldEnforce")) { - bool enforce = config_get_bool(activeConfiguration, "SimpleOutput", "EnforceBitrate"); - config_set_bool(activeConfiguration, "Stream1", "IgnoreRecommended", !enforce); - config_set_bool(activeConfiguration, "Stream1", "MovedOldEnforce", true); - changed = true; - } - - /* ----------------------------------------------------- */ - /* enforce minimum retry delay of 1 second prior to 27.1 */ - if (config_has_user_value(activeConfiguration, "Output", "RetryDelay")) { - int retryDelay = config_get_uint(activeConfiguration, "Output", "RetryDelay"); - if (retryDelay < 1) { - config_set_uint(activeConfiguration, "Output", "RetryDelay", 1); - changed = true; - } - } - - /* ----------------------------------------------------- */ - /* Migrate old container selection (if any) to new key. */ - - auto MigrateFormat = [&](const char *section) { - bool has_old_key = config_has_user_value(activeConfiguration, section, "RecFormat"); - bool has_new_key = config_has_user_value(activeConfiguration, section, "RecFormat2"); - if (!has_new_key && !has_old_key) - return; - - string old_format = - config_get_string(activeConfiguration, section, has_new_key ? "RecFormat2" : "RecFormat"); - string new_format = old_format; - if (old_format == "ts") - new_format = "mpegts"; - else if (old_format == "m3u8") - new_format = "hls"; - else if (old_format == "fmp4") - new_format = "fragmented_mp4"; - else if (old_format == "fmov") - new_format = "fragmented_mov"; - - if (new_format != old_format || !has_new_key) { - config_set_string(activeConfiguration, section, "RecFormat2", new_format.c_str()); - changed = true; - } - }; - - MigrateFormat("AdvOut"); - MigrateFormat("SimpleOutput"); - - /* ----------------------------------------------------- */ - /* Migrate output scale setting to GPU scaling options. */ - - if (config_get_bool(activeConfiguration, "AdvOut", "Rescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RescaleFilter", OBS_SCALE_BILINEAR); - } - - if (config_get_bool(activeConfiguration, "AdvOut", "RecRescale") && - !config_has_user_value(activeConfiguration, "AdvOut", "RecRescaleFilter")) { - config_set_int(activeConfiguration, "AdvOut", "RecRescaleFilter", OBS_SCALE_BILINEAR); - } - - /* ----------------------------------------------------- */ - - if (changed) { - activeConfiguration.SaveSafe("tmp"); - } - - /* ----------------------------------------------------- */ - - config_set_default_string(activeConfiguration, "Output", "Mode", "Simple"); - - config_set_default_bool(activeConfiguration, "Stream1", "IgnoreRecommended", false); - config_set_default_bool(activeConfiguration, "Stream1", "EnableMultitrackVideo", false); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumAggregateBitrateAuto", true); - config_set_default_bool(activeConfiguration, "Stream1", "MultitrackVideoMaximumVideoTracksAuto", true); - - config_set_default_string(activeConfiguration, "SimpleOutput", "FilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_uint(activeConfiguration, "SimpleOutput", "VBitrate", 2500); - config_set_default_uint(activeConfiguration, "SimpleOutput", "ABitrate", 160); - config_set_default_bool(activeConfiguration, "SimpleOutput", "UseAdvanced", false); - config_set_default_string(activeConfiguration, "SimpleOutput", "Preset", "veryfast"); - config_set_default_string(activeConfiguration, "SimpleOutput", "NVENCPreset2", "p5"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecQuality", "Stream"); - config_set_default_bool(activeConfiguration, "SimpleOutput", "RecRB", false); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "SimpleOutput", "RecRBSize", 512); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecRBPrefix", "Replay"); - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamAudioEncoder", "aac"); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecAudioEncoder", "aac"); - config_set_default_uint(activeConfiguration, "SimpleOutput", "RecTracks", (1 << 0)); - - config_set_default_bool(activeConfiguration, "AdvOut", "ApplyServiceSettings", true); - config_set_default_bool(activeConfiguration, "AdvOut", "UseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "TrackIndex", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "VodTrackIndex", 2); - config_set_default_string(activeConfiguration, "AdvOut", "Encoder", "obs_x264"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecType", "Standard"); - - config_set_default_string(activeConfiguration, "AdvOut", "RecFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "RecFormat2", DEFAULT_CONTAINER); - config_set_default_bool(activeConfiguration, "AdvOut", "RecUseRescale", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecTracks", (1 << 0)); - config_set_default_string(activeConfiguration, "AdvOut", "RecEncoder", "none"); - config_set_default_uint(activeConfiguration, "AdvOut", "FLVTrack", 1); - config_set_default_uint(activeConfiguration, "AdvOut", "StreamMultiTrackAudioMixes", 1); - config_set_default_bool(activeConfiguration, "AdvOut", "FFOutputToFile", true); - config_set_default_string(activeConfiguration, "AdvOut", "FFFilePath", GetDefaultVideoSavePath().c_str()); - config_set_default_string(activeConfiguration, "AdvOut", "FFExtension", "mp4"); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVBitrate", 2500); - config_set_default_uint(activeConfiguration, "AdvOut", "FFVGOPSize", 250); - config_set_default_bool(activeConfiguration, "AdvOut", "FFUseRescale", false); - config_set_default_bool(activeConfiguration, "AdvOut", "FFIgnoreCompat", false); - config_set_default_uint(activeConfiguration, "AdvOut", "FFABitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "FFAudioMixes", 1); - - config_set_default_uint(activeConfiguration, "AdvOut", "Track1Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track2Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track3Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track4Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track5Bitrate", 160); - config_set_default_uint(activeConfiguration, "AdvOut", "Track6Bitrate", 160); - - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileTime", 15); - config_set_default_uint(activeConfiguration, "AdvOut", "RecSplitFileSize", 2048); - - config_set_default_bool(activeConfiguration, "AdvOut", "RecRB", false); - config_set_default_uint(activeConfiguration, "AdvOut", "RecRBTime", 20); - config_set_default_int(activeConfiguration, "AdvOut", "RecRBSize", 512); - - config_set_default_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_default_uint(activeConfiguration, "Video", "BaseCY", cy); - - /* don't allow BaseCX/BaseCY to be susceptible to defaults changing */ - if (!config_has_user_value(activeConfiguration, "Video", "BaseCX") || - !config_has_user_value(activeConfiguration, "Video", "BaseCY")) { - config_set_uint(activeConfiguration, "Video", "BaseCX", cx); - config_set_uint(activeConfiguration, "Video", "BaseCY", cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_string(activeConfiguration, "Output", "FilenameFormatting", "%CCYY-%MM-%DD %hh-%mm-%ss"); - - config_set_default_bool(activeConfiguration, "Output", "DelayEnable", false); - config_set_default_uint(activeConfiguration, "Output", "DelaySec", 20); - config_set_default_bool(activeConfiguration, "Output", "DelayPreserve", true); - - config_set_default_bool(activeConfiguration, "Output", "Reconnect", true); - config_set_default_uint(activeConfiguration, "Output", "RetryDelay", 2); - config_set_default_uint(activeConfiguration, "Output", "MaxRetries", 25); - - config_set_default_string(activeConfiguration, "Output", "BindIP", "default"); - config_set_default_string(activeConfiguration, "Output", "IPFamily", "IPv4+IPv6"); - config_set_default_bool(activeConfiguration, "Output", "NewSocketLoopEnable", false); - config_set_default_bool(activeConfiguration, "Output", "LowLatencyEnable", false); - - int i = 0; - uint32_t scale_cx = cx; - uint32_t scale_cy = cy; - - /* use a default scaled resolution that has a pixel count no higher - * than 1280x720 */ - while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) { - double scale = scaled_vals[i++]; - scale_cx = uint32_t(double(cx) / scale); - scale_cy = uint32_t(double(cy) / scale); - } - - config_set_default_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_default_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - - /* don't allow OutputCX/OutputCY to be susceptible to defaults - * changing */ - if (!config_has_user_value(activeConfiguration, "Video", "OutputCX") || - !config_has_user_value(activeConfiguration, "Video", "OutputCY")) { - config_set_uint(activeConfiguration, "Video", "OutputCX", scale_cx); - config_set_uint(activeConfiguration, "Video", "OutputCY", scale_cy); - config_save_safe(activeConfiguration, "tmp", nullptr); - } - - config_set_default_uint(activeConfiguration, "Video", "FPSType", 0); - config_set_default_string(activeConfiguration, "Video", "FPSCommon", "30"); - config_set_default_uint(activeConfiguration, "Video", "FPSInt", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSNum", 30); - config_set_default_uint(activeConfiguration, "Video", "FPSDen", 1); - config_set_default_string(activeConfiguration, "Video", "ScaleType", "bicubic"); - config_set_default_string(activeConfiguration, "Video", "ColorFormat", "NV12"); - config_set_default_string(activeConfiguration, "Video", "ColorSpace", "709"); - config_set_default_string(activeConfiguration, "Video", "ColorRange", "Partial"); - config_set_default_uint(activeConfiguration, "Video", "SdrWhiteLevel", 300); - config_set_default_uint(activeConfiguration, "Video", "HdrNominalPeakLevel", 1000); - - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceId", "default"); - config_set_default_string(activeConfiguration, "Audio", "MonitoringDeviceName", - Str("Basic.Settings.Advanced.Audio.MonitoringDevice" - ".Default")); - config_set_default_uint(activeConfiguration, "Audio", "SampleRate", 48000); - config_set_default_string(activeConfiguration, "Audio", "ChannelSetup", "Stereo"); - config_set_default_double(activeConfiguration, "Audio", "MeterDecayRate", VOLUME_METER_DECAY_FAST); - config_set_default_uint(activeConfiguration, "Audio", "PeakMeterType", 0); - - CheckExistingCookieId(); - - return true; -} - -extern bool EncoderAvailable(const char *encoder); - -void OBSBasic::InitBasicConfigDefaults2() -{ - bool oldEncDefaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - bool useNV = EncoderAvailable("ffmpeg_nvenc") && !oldEncDefaults; - - config_set_default_string(activeConfiguration, "SimpleOutput", "StreamEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - config_set_default_string(activeConfiguration, "SimpleOutput", "RecEncoder", - useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); - - const char *aac_default = "ffmpeg_aac"; - if (EncoderAvailable("CoreAudio_AAC")) - aac_default = "CoreAudio_AAC"; - else if (EncoderAvailable("libfdk_aac")) - aac_default = "libfdk_aac"; - - config_set_default_string(activeConfiguration, "AdvOut", "AudioEncoder", aac_default); - config_set_default_string(activeConfiguration, "AdvOut", "RecAudioEncoder", aac_default); -} - -bool OBSBasic::InitBasicConfig() -{ - ProfileScope("OBSBasic::InitBasicConfig"); - - RefreshProfiles(true); - - const std::string currentProfileName{config_get_string(App()->GetUserConfig(), "Basic", "Profile")}; - const std::optional currentProfile = GetProfileByName(currentProfileName); - const std::optional foundProfile = GetProfileByName(opt_starting_profile); - - try { - if (foundProfile) { - ActivateProfile(foundProfile.value()); - } else if (currentProfile) { - ActivateProfile(currentProfile.value()); - } else { - const OBSProfile &newProfile = CreateProfile(currentProfileName); - ActivateProfile(newProfile); - } - } catch (const std::logic_error &) { - OBSErrorBox(NULL, "Failed to open basic.ini: %d", -1); - return false; - } - - return true; -} - -void OBSBasic::InitOBSCallbacks() -{ - ProfileScope("OBSBasic::InitOBSCallbacks"); - - signalHandlers.reserve(signalHandlers.size() + 9); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_create", OBSBasic::SourceCreated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_remove", OBSBasic::SourceRemoved, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_activate", OBSBasic::SourceActivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_deactivate", OBSBasic::SourceDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_activate", OBSBasic::SourceAudioActivated, - this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_audio_deactivate", - OBSBasic::SourceAudioDeactivated, this); - signalHandlers.emplace_back(obs_get_signal_handler(), "source_rename", OBSBasic::SourceRenamed, this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_add", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); - signalHandlers.emplace_back( - obs_get_signal_handler(), "source_filter_remove", - [](void *data, calldata_t *) { - QMetaObject::invokeMethod(static_cast(data), "UpdateEditMenu", - Qt::QueuedConnection); - }, - this); -} - -void OBSBasic::InitPrimitives() -{ - ProfileScope("OBSBasic::InitPrimitives"); - - obs_enter_graphics(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - box = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(0.0f, 1.0f); - boxLeft = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 0.0f); - gs_vertex2f(1.0f, 0.0f); - boxTop = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(1.0f, 0.0f); - gs_vertex2f(1.0f, 1.0f); - boxRight = gs_render_save(); - - gs_render_start(true); - gs_vertex2f(0.0f, 1.0f); - gs_vertex2f(1.0f, 1.0f); - boxBottom = gs_render_save(); - - gs_render_start(true); - for (int i = 0; i <= 360; i += (360 / 20)) { - float pos = RAD(float(i)); - gs_vertex2f(cosf(pos), sinf(pos)); - } - circle = gs_render_save(); - - InitSafeAreas(&actionSafeMargin, &graphicsSafeMargin, &fourByThreeSafeMargin, &leftLine, &topLine, &rightLine); - obs_leave_graphics(); -} - -void OBSBasic::ReplayBufferActionTriggered() -{ - if (outputHandler->ReplayBufferActive()) - StopReplayBuffer(); - else - StartReplayBuffer(); -}; - -void OBSBasic::ResetOutputs() -{ - ProfileScope("OBSBasic::ResetOutputs"); - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool advOut = astrcmpi(mode, "Advanced") == 0; - - if ((!outputHandler || !outputHandler->Active()) && - (!setupStreamingGuard.valid() || - setupStreamingGuard.wait_for(std::chrono::seconds{0}) == std::future_status::ready)) { - outputHandler.reset(); - outputHandler.reset(advOut ? CreateAdvancedOutputHandler(this) : CreateSimpleOutputHandler(this)); - - emit ReplayBufEnabled(outputHandler->replayBuffer); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setEnabled(!!outputHandler->replayBuffer); - - UpdateIsRecordingPausable(); - } else { - outputHandler->Update(); - } -} - -#define STARTUP_SEPARATOR "==== Startup complete ===============================================" -#define SHUTDOWN_SEPARATOR "==== Shutting down ==================================================" - -#define UNSUPPORTED_ERROR \ - "Failed to initialize video:\n\nRequired graphics API functionality " \ - "not found. Your GPU may not be supported." - -#define UNKNOWN_ERROR \ - "Failed to initialize video. Your GPU may not be supported, " \ - "or your graphics drivers may need to be updated." - -static inline void LogEncoders() -{ - constexpr uint32_t hide_flags = OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL; - - auto list_encoders = [](obs_encoder_type type) { - size_t idx = 0; - const char *encoder_type; - - while (obs_enum_encoder_types(idx++, &encoder_type)) { - if (obs_get_encoder_caps(encoder_type) & hide_flags || - obs_get_encoder_type(encoder_type) != type) { - continue; - } - - blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type)); - } - }; - - blog(LOG_INFO, "---------------------------------"); - blog(LOG_INFO, "Available Encoders:"); - blog(LOG_INFO, " Video Encoders:"); - list_encoders(OBS_ENCODER_VIDEO); - blog(LOG_INFO, " Audio Encoders:"); - list_encoders(OBS_ENCODER_AUDIO); -} - -void OBSBasic::OBSInit() -{ - ProfileScope("OBSBasic::OBSInit"); - - if (!InitBasicConfig()) - throw "Failed to load basic.ini"; - if (!ResetAudio()) - throw "Failed to initialize audio"; - - int ret = 0; - - ret = ResetVideo(); - - switch (ret) { - case OBS_VIDEO_MODULE_NOT_FOUND: - throw "Failed to initialize video: Graphics module not found"; - case OBS_VIDEO_NOT_SUPPORTED: - throw UNSUPPORTED_ERROR; - case OBS_VIDEO_INVALID_PARAM: - throw "Failed to initialize video: Invalid parameters"; - default: - if (ret != OBS_VIDEO_SUCCESS) - throw UNKNOWN_ERROR; - } - - /* load audio monitoring */ - if (obs_audio_monitoring_available()) { - const char *device_name = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceName"); - const char *device_id = config_get_string(activeConfiguration, "Audio", "MonitoringDeviceId"); - - obs_set_audio_monitoring_device(device_name, device_id); - - blog(LOG_INFO, "Audio monitoring device:\n\tname: %s\n\tid: %s", device_name, device_id); - } - - InitOBSCallbacks(); - InitHotkeys(); - ui->preview->Init(); - - /* hack to prevent elgato from loading its own QtNetwork that it tries - * to ship with */ -#if defined(_WIN32) && !defined(_DEBUG) - LoadLibraryW(L"Qt6Network"); -#endif - struct obs_module_failure_info mfi; - - /* Safe Mode disables third-party plugins so we don't need to add earch - * paths outside the OBS bundle/installation. */ - if (safe_mode || disable_3p_plugins) { - SetSafeModuleNames(); - } else { - AddExtraModulePaths(); - } - - /* Modules can access frontend information (i.e. profile and scene collection data) during their initialization, and some modules (e.g. obs-websockets) are known to use the filesystem location of the current profile in their own code. - - Thus the profile and scene collection discovery needs to happen before any access to that information (but after intializing global settings) to ensure legacy code gets valid path information. - */ - RefreshSceneCollections(true); - - blog(LOG_INFO, "---------------------------------"); - obs_load_all_modules2(&mfi); - blog(LOG_INFO, "---------------------------------"); - obs_log_loaded_modules(); - blog(LOG_INFO, "---------------------------------"); - obs_post_load_modules(); - - BPtr failed_modules = mfi.failed_modules; - -#ifdef BROWSER_AVAILABLE - cef = obs_browser_init_panel(); - cef_js_avail = cef && obs_browser_qcef_version() >= 3; -#endif - - vcamEnabled = (obs_get_output_flags(VIRTUAL_CAM_ID) & OBS_OUTPUT_VIDEO) != 0; - if (vcamEnabled) { - emit VirtualCamEnabled(); - } - - UpdateProfileEncoders(); - - LogEncoders(); - - blog(LOG_INFO, STARTUP_SEPARATOR); - - if (!InitService()) - throw "Failed to initialize service"; - - ResetOutputs(); - CreateHotkeys(); - - InitPrimitives(); - - sceneDuplicationMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode"); - swapScenesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode"); - editPropertiesMode = config_get_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode"); - - if (!opt_studio_mode) { - SetPreviewProgramMode(config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode")); - } else { - SetPreviewProgramMode(true); - opt_studio_mode = false; - } - -#define SET_VISIBILITY(name, control) \ - do { \ - if (config_has_user_value(App()->GetUserConfig(), "BasicWindow", name)) { \ - bool visible = config_get_bool(App()->GetUserConfig(), "BasicWindow", name); \ - ui->control->setChecked(visible); \ - } \ - } while (false) - - SET_VISIBILITY("ShowListboxToolbars", toggleListboxToolbars); - SET_VISIBILITY("ShowStatusBar", toggleStatusBar); -#undef SET_VISIBILITY - - bool sourceIconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - ui->toggleSourceIcons->setChecked(sourceIconsVisible); - - bool contextVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars"); - ui->toggleContextBar->setChecked(contextVisible); - ui->contextContainer->setVisible(contextVisible); - if (contextVisible) - UpdateContextBar(true); - UpdateEditMenu(); - - { - ProfileScope("OBSBasic::Load"); - const std::string sceneCollectionName{ - config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection")}; - const std::optional configuredCollection = - GetSceneCollectionByName(sceneCollectionName); - const std::optional foundCollection = - GetSceneCollectionByName(opt_starting_collection); - - if (foundCollection) { - ActivateSceneCollection(foundCollection.value()); - } else if (configuredCollection) { - ActivateSceneCollection(configuredCollection.value()); - } else { - disableSaving--; - SetupNewSceneCollection(sceneCollectionName); - disableSaving++; - } - - disableSaving--; - if (foundCollection || configuredCollection) { - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED); - } - OnEvent(OBS_FRONTEND_EVENT_SCENE_CHANGED); - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - disableSaving++; - } - - loaded = true; - - previewEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled"); - - if (!previewEnabled && !IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, - Q_ARG(bool, previewEnabled)); - else if (!previewEnabled && IsPreviewProgramMode()) - QMetaObject::invokeMethod(this, "EnablePreviewDisplay", Qt::QueuedConnection, Q_ARG(bool, true)); - - disableSaving--; - - auto addDisplay = [this](OBSQTDisplay *window) { - obs_display_add_draw_callback(window->GetDisplay(), OBSBasic::RenderMain, this); - - struct obs_video_info ovi; - if (obs_get_video_info(&ovi)) - ResizePreview(ovi.base_width, ovi.base_height); - }; - - connect(ui->preview, &OBSQTDisplay::DisplayCreated, addDisplay); - - /* Show the main window, unless the tray icon isn't available - * or neither the setting nor flag for starting minimized is set. */ - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool hideWindowOnStart = QSystemTrayIcon::isSystemTrayAvailable() && sysTrayEnabled && - (opt_minimize_tray || sysTrayWhenStarted); - -#ifdef _WIN32 - SetWin32DropStyle(this); - - if (!hideWindowOnStart) - show(); -#endif - - bool alwaysOnTop = config_get_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop"); - -#ifdef ENABLE_WAYLAND - bool isWayland = obs_get_nix_platform() == OBS_NIX_PLATFORM_WAYLAND; -#else - bool isWayland = false; -#endif - - if (!isWayland && (alwaysOnTop || opt_always_on_top)) { - SetAlwaysOnTop(this, true); - ui->actionAlwaysOnTop->setChecked(true); - } else if (isWayland) { - if (opt_always_on_top) - blog(LOG_INFO, "Always On Top not available on Wayland, ignoring."); - ui->actionAlwaysOnTop->setEnabled(false); - ui->actionAlwaysOnTop->setVisible(false); - } - -#ifndef _WIN32 - if (!hideWindowOnStart) - show(); -#endif - - /* setup stats dock */ - OBSBasicStats *statsDlg = new OBSBasicStats(statsDock, false); - statsDock->setWidget(statsDlg); - - /* ----------------------------- */ - /* add custom browser docks */ -#if defined(BROWSER_AVAILABLE) && defined(YOUTUBE_ENABLED) - YouTubeAppDock::CleanupYouTubeUrls(); -#endif - -#ifdef BROWSER_AVAILABLE - if (cef) { - QAction *action = new QAction(QTStr("Basic.MainMenu.Docks." - "CustomBrowserDocks"), - this); - ui->menuDocks->insertAction(ui->scenesDock->toggleViewAction(), action); - connect(action, &QAction::triggered, this, &OBSBasic::ManageExtraBrowserDocks); - ui->menuDocks->insertSeparator(ui->scenesDock->toggleViewAction()); - - LoadExtraBrowserDocks(); - } -#endif - -#ifdef YOUTUBE_ENABLED - /* setup YouTube app dock */ - if (YouTubeAppDock::IsYTServiceSelected()) - NewYouTubeAppDock(); -#endif - - const char *dockStateStr = config_get_string(App()->GetUserConfig(), "BasicWindow", "DockState"); - - if (!dockStateStr) { - on_resetDocks_triggered(true); - } else { - QByteArray dockState = QByteArray::fromBase64(QByteArray(dockStateStr)); - if (!restoreState(dockState)) - on_resetDocks_triggered(true); - } - - bool pre23Defaults = config_get_bool(App()->GetUserConfig(), "General", "Pre23Defaults"); - if (pre23Defaults) { - bool resetDockLock23 = config_get_bool(App()->GetUserConfig(), "General", "ResetDockLock23"); - if (!resetDockLock23) { - config_set_bool(App()->GetUserConfig(), "General", "ResetDockLock23", true); - config_remove_value(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - } - - bool docksLocked = config_get_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked"); - on_lockDocks_toggled(docksLocked); - ui->lockDocks->blockSignals(true); - ui->lockDocks->setChecked(docksLocked); - ui->lockDocks->blockSignals(false); - - bool sideDocks = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks"); - on_sideDocks_toggled(sideDocks); - ui->sideDocks->blockSignals(true); - ui->sideDocks->setChecked(sideDocks); - ui->sideDocks->blockSignals(false); - - SystemTray(true); - - TaskbarOverlayInit(); - -#ifdef __APPLE__ - disableColorSpaceConversion(this); -#endif - - bool has_last_version = config_has_user_value(App()->GetAppConfig(), "General", "LastVersion"); - bool first_run = config_get_bool(App()->GetUserConfig(), "General", "FirstRun"); - - if (!first_run) { - config_set_bool(App()->GetUserConfig(), "General", "FirstRun", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - - if (!first_run && !has_last_version && !Active()) - QMetaObject::invokeMethod(this, "on_autoConfigure_triggered", Qt::QueuedConnection); - -#if (defined(_WIN32) || defined(__APPLE__)) && (OBS_RELEASE_CANDIDATE > 0 || OBS_BETA > 0) - /* Automatically set branch to "beta" the first time a pre-release build is run. */ - if (!config_get_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn")) { - config_set_string(App()->GetAppConfig(), "General", "UpdateBranch", "beta"); - config_set_bool(App()->GetAppConfig(), "General", "AutoBetaOptIn", true); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - } -#endif - TimedCheckForUpdates(); - - ToggleMixerLayout(config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - if (config_get_bool(activeConfiguration, "General", "OpenStatsOnStartup")) - on_stats_triggered(); - - OBSBasicStats::InitializeValues(); - - /* ----------------------- */ - /* Add multiview menu */ - - ui->viewMenu->addSeparator(); - - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); - connect(ui->viewMenu->menuAction(), &QAction::hovered, this, &OBSBasic::UpdateMultiviewProjectorMenu); - - ui->sources->UpdateIcons(); - -#if !defined(_WIN32) - delete ui->actionShowCrashLogs; - delete ui->actionUploadLastCrashLog; - delete ui->menuCrashLogs; - delete ui->actionRepair; - ui->actionShowCrashLogs = nullptr; - ui->actionUploadLastCrashLog = nullptr; - ui->menuCrashLogs = nullptr; - ui->actionRepair = nullptr; -#if !defined(__APPLE__) - delete ui->actionCheckForUpdates; - ui->actionCheckForUpdates = nullptr; -#endif -#endif - -#ifdef __APPLE__ - /* Remove OBS' Fullscreen Interface menu in favor of the one macOS adds by default */ - delete ui->actionFullscreenInterface; - ui->actionFullscreenInterface = nullptr; -#else - /* Don't show menu to raise macOS-only permissions dialog */ - delete ui->actionShowMacPermissions; - ui->actionShowMacPermissions = nullptr; -#endif - -#if defined(_WIN32) || defined(__APPLE__) - if (App()->IsUpdaterDisabled()) { - ui->actionCheckForUpdates->setEnabled(false); -#if defined(_WIN32) - ui->actionRepair->setEnabled(false); -#endif - } -#endif - -#ifndef WHATSNEW_ENABLED - delete ui->actionShowWhatsNew; - ui->actionShowWhatsNew = nullptr; -#endif - - if (safe_mode) { - ui->actionRestartSafe->setText(QTStr("Basic.MainMenu.Help.RestartNormal")); - } - - UpdatePreviewProgramIndicators(); - OnFirstLoad(); - - if (!hideWindowOnStart) - activateWindow(); - - /* ------------------------------------------- */ - /* display warning message for failed modules */ - - if (mfi.count) { - QString failed_plugins; - - char **plugin = mfi.failed_modules; - while (*plugin) { - failed_plugins += *plugin; - failed_plugins += "\n"; - plugin++; - } - - QString failed_msg = QTStr("PluginsFailedToLoad.Text").arg(failed_plugins); - OBSMessageBox::warning(this, QTStr("PluginsFailedToLoad.Title"), failed_msg); - } -} - -void OBSBasic::OnFirstLoad() -{ - OnEvent(OBS_FRONTEND_EVENT_FINISHED_LOADING); - -#ifdef WHATSNEW_ENABLED - /* Attempt to load init screen if available */ - if (cef) { - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); - } -#endif - - Auth::Load(); - - bool showLogViewerOnStartup = config_get_bool(App()->GetUserConfig(), "LogViewer", "ShowLogStartup"); - - if (showLogViewerOnStartup) - on_actionViewCurrentLog_triggered(); -} - -/* shows a "what's new" page on startup of new versions using CEF */ -void OBSBasic::ReceivedIntroJson(const QString &text) -{ -#ifdef WHATSNEW_ENABLED - if (closing) - return; - - WhatsNewList items; - try { - nlohmann::json json = nlohmann::json::parse(text.toStdString()); - items = json.get(); - } catch (nlohmann::json::exception &e) { - blog(LOG_WARNING, "Parsing whatsnew data failed: %s", e.what()); - return; - } - - std::string info_url; - int info_increment = -1; - - /* check to see if there's an info page for this version */ - for (const WhatsNewItem &item : items) { - if (item.os) { - WhatsNewPlatforms platforms = *item.os; -#ifdef _WIN32 - if (!platforms.windows) - continue; -#elif defined(__APPLE__) - if (!platforms.macos) - continue; -#else - if (!platforms.linux) - continue; -#endif - } - - int major = 0; - int minor = 0; - - sscanf(item.version.c_str(), "%d.%d", &major, &minor); - if (major == LIBOBS_API_MAJOR_VER && minor == LIBOBS_API_MINOR_VER && - item.RC == OBS_RELEASE_CANDIDATE && item.Beta == OBS_BETA) { - info_url = item.url; - info_increment = item.increment; - } - } - - /* this version was not found, or no info for this version */ - if (info_increment == -1) { - return; - } - -#if OBS_RELEASE_CANDIDATE > 0 - constexpr const char *lastInfoVersion = "InfoLastRCVersion"; -#elif OBS_BETA > 0 - constexpr const char *lastInfoVersion = "InfoLastBetaVersion"; -#else - constexpr const char *lastInfoVersion = "InfoLastVersion"; -#endif - constexpr uint64_t currentVersion = (uint64_t)LIBOBS_API_VER << 16ULL | OBS_RELEASE_CANDIDATE << 8ULL | - OBS_BETA; - uint64_t lastVersion = config_get_uint(App()->GetAppConfig(), "General", lastInfoVersion); - int current_version_increment = -1; - - if ((lastVersion & ~0xFFFF0000ULL) < (currentVersion & ~0xFFFF0000ULL)) { - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - config_set_uint(App()->GetAppConfig(), "General", lastInfoVersion, currentVersion); - } else { - current_version_increment = config_get_int(App()->GetAppConfig(), "General", "InfoIncrement"); - } - - if (info_increment <= current_version_increment) { - return; - } - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", info_increment); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - cef->init_browser(); - - WhatsNewBrowserInitThread *wnbit = new WhatsNewBrowserInitThread(QT_UTF8(info_url.c_str())); - - connect(wnbit, &WhatsNewBrowserInitThread::Result, this, &OBSBasic::ShowWhatsNew, Qt::QueuedConnection); - - whatsNewInitThread.reset(wnbit); - whatsNewInitThread->start(); - -#else - UNUSED_PARAMETER(text); -#endif -} - -void OBSBasic::ShowWhatsNew(const QString &url) -{ -#ifdef BROWSER_AVAILABLE - if (closing) - return; - - if (obsWhatsNew) { - obsWhatsNew->close(); - } - - obsWhatsNew = new OBSWhatsNew(this, QT_TO_UTF8(url)); -#else - UNUSED_PARAMETER(url); -#endif -} - -void OBSBasic::UpdateMultiviewProjectorMenu() -{ - ui->multiviewProjectorMenu->clear(); - AddProjectorMenuMonitors(ui->multiviewProjectorMenu, this, &OBSBasic::OpenMultiviewProjector); -} - -void OBSBasic::InitHotkeys() -{ - ProfileScope("OBSBasic::InitHotkeys"); - - struct obs_hotkeys_translations t = {}; - t.insert = Str("Hotkeys.Insert"); - t.del = Str("Hotkeys.Delete"); - t.home = Str("Hotkeys.Home"); - t.end = Str("Hotkeys.End"); - t.page_up = Str("Hotkeys.PageUp"); - t.page_down = Str("Hotkeys.PageDown"); - t.num_lock = Str("Hotkeys.NumLock"); - t.scroll_lock = Str("Hotkeys.ScrollLock"); - t.caps_lock = Str("Hotkeys.CapsLock"); - t.backspace = Str("Hotkeys.Backspace"); - t.tab = Str("Hotkeys.Tab"); - t.print = Str("Hotkeys.Print"); - t.pause = Str("Hotkeys.Pause"); - t.left = Str("Hotkeys.Left"); - t.right = Str("Hotkeys.Right"); - t.up = Str("Hotkeys.Up"); - t.down = Str("Hotkeys.Down"); -#ifdef _WIN32 - t.meta = Str("Hotkeys.Windows"); -#else - t.meta = Str("Hotkeys.Super"); -#endif - t.menu = Str("Hotkeys.Menu"); - t.space = Str("Hotkeys.Space"); - t.numpad_num = Str("Hotkeys.NumpadNum"); - t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); - t.numpad_divide = Str("Hotkeys.NumpadDivide"); - t.numpad_plus = Str("Hotkeys.NumpadAdd"); - t.numpad_minus = Str("Hotkeys.NumpadSubtract"); - t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); - t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); - t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); - t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); - t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); - t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); - t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); - t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); - t.mouse_num = Str("Hotkeys.MouseButton"); - t.escape = Str("Hotkeys.Escape"); - obs_hotkeys_set_translations(&t); - - obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), Str("Push-to-mute"), - Str("Push-to-talk")); - - obs_hotkeys_set_sceneitem_hotkeys_translations(Str("SceneItemShow"), Str("SceneItemHide")); - - obs_hotkey_enable_callback_rerouting(true); - obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); -} - -void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) -{ - obs_hotkey_trigger_routed_callback(id, pressed); -} - -void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) -{ - OBSBasic &basic = *static_cast(data); - QMetaObject::invokeMethod(&basic, "ProcessHotkey", Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); -} - -void OBSBasic::CreateHotkeys() -{ - ProfileScope("OBSBasic::CreateHotkeys"); - - auto LoadHotkeyData = [&](const char *name) -> OBSData { - const char *info = config_get_string(activeConfiguration, "Hotkeys", name); - if (!info) - return {}; - - OBSDataAutoRelease data = obs_data_create_from_json(info); - if (!data) - return {}; - - return data.Get(); - }; - - auto LoadHotkey = [&](obs_hotkey_id id, const char *name) { - OBSDataArrayAutoRelease array = obs_data_get_array(LoadHotkeyData(name), "bindings"); - - obs_hotkey_load(id, array); - }; - - auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, const char *name1, - const char *oldName = NULL) { - if (oldName) { - const auto info = config_get_string(activeConfiguration, "Hotkeys", oldName); - if (info) { - config_set_string(activeConfiguration, "Hotkeys", name0, info); - config_set_string(activeConfiguration, "Hotkeys", name1, info); - config_remove_value(activeConfiguration, "Hotkeys", oldName); - activeConfiguration.Save(); - } - } - OBSDataArrayAutoRelease array0 = obs_data_get_array(LoadHotkeyData(name0), "bindings"); - OBSDataArrayAutoRelease array1 = obs_data_get_array(LoadHotkeyData(name1), "bindings"); - - obs_hotkey_pair_load(id, array0, array1); - }; - -#define MAKE_CALLBACK(pred, method, log_action) \ - [](void *data, obs_hotkey_pair_id, obs_hotkey_t *, bool pressed) { \ - OBSBasic &basic = *static_cast(data); \ - if ((pred) && pressed) { \ - blog(LOG_INFO, log_action " due to hotkey"); \ - method(); \ - return true; \ - } \ - return false; \ - } - - streamingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartStreaming", Str("Basic.Main.StartStreaming"), "OBSBasic.StopStreaming", - Str("Basic.Main.StopStreaming"), - MAKE_CALLBACK(!basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StartStreaming, - "Starting stream"), - MAKE_CALLBACK(basic.outputHandler->StreamingActive() && !basic.streamingStarting, basic.StopStreaming, - "Stopping stream"), - this, this); - LoadHotkeyPair(streamingHotkeys, "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); - - auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic &basic = *static_cast(data); - if (basic.outputHandler->StreamingActive() && pressed) { - basic.ForceStopStreaming(); - } - }; - - forceStreamingStopHotkey = obs_hotkey_register_frontend("OBSBasic.ForceStopStreaming", - Str("Basic.Main.ForceStopStreaming"), cb, this); - LoadHotkey(forceStreamingStopHotkey, "OBSBasic.ForceStopStreaming"); - - recordingHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartRecording", Str("Basic.Main.StartRecording"), "OBSBasic.StopRecording", - Str("Basic.Main.StopRecording"), - MAKE_CALLBACK(!basic.outputHandler->RecordingActive() && !basic.recordingStarted, basic.StartRecording, - "Starting recording"), - MAKE_CALLBACK(basic.outputHandler->RecordingActive() && basic.recordingStarted, basic.StopRecording, - "Stopping recording"), - this, this); - LoadHotkeyPair(recordingHotkeys, "OBSBasic.StartRecording", "OBSBasic.StopRecording"); - - pauseHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.PauseRecording", Str("Basic.Main.PauseRecording"), - "OBSBasic.UnpauseRecording", Str("Basic.Main.UnpauseRecording"), - MAKE_CALLBACK(basic.isRecordingPausable && !basic.recordingPaused, - basic.PauseRecording, "Pausing recording"), - MAKE_CALLBACK(basic.isRecordingPausable && basic.recordingPaused, - basic.UnpauseRecording, "Unpausing recording"), - this, this); - LoadHotkeyPair(pauseHotkeys, "OBSBasic.PauseRecording", "OBSBasic.UnpauseRecording"); - - splitFileHotkey = obs_hotkey_register_frontend( - "OBSBasic.SplitFile", Str("Basic.Main.SplitFile"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_split_file(); - }, - this); - LoadHotkey(splitFileHotkey, "OBSBasic.SplitFile"); - - addChapterHotkey = obs_hotkey_register_frontend( - "OBSBasic.AddChapterMarker", Str("Basic.Main.AddChapterMarker"), - [](void *, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - obs_frontend_recording_add_chapter(nullptr); - }, - this); - LoadHotkey(addChapterHotkey, "OBSBasic.AddChapterMarker"); - - replayBufHotkeys = - obs_hotkey_pair_register_frontend("OBSBasic.StartReplayBuffer", Str("Basic.Main.StartReplayBuffer"), - "OBSBasic.StopReplayBuffer", Str("Basic.Main.StopReplayBuffer"), - MAKE_CALLBACK(!basic.outputHandler->ReplayBufferActive(), - basic.StartReplayBuffer, "Starting replay buffer"), - MAKE_CALLBACK(basic.outputHandler->ReplayBufferActive(), - basic.StopReplayBuffer, "Stopping replay buffer"), - this, this); - LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); - - if (vcamEnabled) { - vcamHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.StartVirtualCam", Str("Basic.Main.StartVirtualCam"), "OBSBasic.StopVirtualCam", - Str("Basic.Main.StopVirtualCam"), - MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), basic.StartVirtualCam, - "Starting virtual camera"), - MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), basic.StopVirtualCam, - "Stopping virtual camera"), - this, this); - LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", "OBSBasic.StopVirtualCam"); - } - - togglePreviewHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), "OBSBasic.DisablePreview", - Str("Basic.Main.Preview.Disable"), - MAKE_CALLBACK(!basic.previewEnabled, basic.EnablePreview, "Enabling preview"), - MAKE_CALLBACK(basic.previewEnabled, basic.DisablePreview, "Disabling preview"), this, this); - LoadHotkeyPair(togglePreviewHotkeys, "OBSBasic.EnablePreview", "OBSBasic.DisablePreview"); - - togglePreviewProgramHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.EnablePreviewProgram", Str("Basic.EnablePreviewProgramMode"), - "OBSBasic.DisablePreviewProgram", Str("Basic.DisablePreviewProgramMode"), - MAKE_CALLBACK(!basic.IsPreviewProgramMode(), basic.EnablePreviewProgram, "Enabling preview program"), - MAKE_CALLBACK(basic.IsPreviewProgramMode(), basic.DisablePreviewProgram, "Disabling preview program"), - this, this); - LoadHotkeyPair(togglePreviewProgramHotkeys, "OBSBasic.EnablePreviewProgram", "OBSBasic.DisablePreviewProgram", - "OBSBasic.TogglePreviewProgram"); - - contextBarHotkeys = obs_hotkey_pair_register_frontend( - "OBSBasic.ShowContextBar", Str("Basic.Main.ShowContextBar"), "OBSBasic.HideContextBar", - Str("Basic.Main.HideContextBar"), - MAKE_CALLBACK(!basic.ui->contextContainer->isVisible(), basic.ShowContextBar, "Showing Context Bar"), - MAKE_CALLBACK(basic.ui->contextContainer->isVisible(), basic.HideContextBar, "Hiding Context Bar"), - this, this); - LoadHotkeyPair(contextBarHotkeys, "OBSBasic.ShowContextBar", "OBSBasic.HideContextBar"); -#undef MAKE_CALLBACK - - auto transition = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "TransitionClicked", - Qt::QueuedConnection); - }; - - transitionHotkey = obs_hotkey_register_frontend("OBSBasic.Transition", Str("Transition"), transition, this); - LoadHotkey(transitionHotkey, "OBSBasic.Transition"); - - auto resetStats = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ResetStatsHotkey", - Qt::QueuedConnection); - }; - - statsHotkey = - obs_hotkey_register_frontend("OBSBasic.ResetStats", Str("Basic.Stats.ResetStats"), resetStats, this); - LoadHotkey(statsHotkey, "OBSBasic.ResetStats"); - - auto screenshot = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "Screenshot", Qt::QueuedConnection); - }; - - screenshotHotkey = obs_hotkey_register_frontend("OBSBasic.Screenshot", Str("Screenshot"), screenshot, this); - LoadHotkey(screenshotHotkey, "OBSBasic.Screenshot"); - - auto screenshotSource = [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - if (pressed) - QMetaObject::invokeMethod(static_cast(data), "ScreenshotSelectedSource", - Qt::QueuedConnection); - }; - - sourceScreenshotHotkey = obs_hotkey_register_frontend("OBSBasic.SelectedSourceScreenshot", - Str("Screenshot.SourceHotkey"), screenshotSource, this); - LoadHotkey(sourceScreenshotHotkey, "OBSBasic.SelectedSourceScreenshot"); -} - -void OBSBasic::ClearHotkeys() -{ - obs_hotkey_pair_unregister(streamingHotkeys); - obs_hotkey_pair_unregister(recordingHotkeys); - obs_hotkey_pair_unregister(pauseHotkeys); - obs_hotkey_unregister(splitFileHotkey); - obs_hotkey_unregister(addChapterHotkey); - obs_hotkey_pair_unregister(replayBufHotkeys); - obs_hotkey_pair_unregister(vcamHotkeys); - obs_hotkey_pair_unregister(togglePreviewHotkeys); - obs_hotkey_pair_unregister(contextBarHotkeys); - obs_hotkey_pair_unregister(togglePreviewProgramHotkeys); - obs_hotkey_unregister(forceStreamingStopHotkey); - obs_hotkey_unregister(transitionHotkey); - obs_hotkey_unregister(statsHotkey); - obs_hotkey_unregister(screenshotHotkey); - obs_hotkey_unregister(sourceScreenshotHotkey); -} - -OBSBasic::~OBSBasic() -{ - /* clear out UI event queue */ - QApplication::sendPostedEvents(nullptr); - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - if (updateCheckThread && updateCheckThread->isRunning()) - updateCheckThread->wait(); - - if (patronJsonThread && patronJsonThread->isRunning()) - patronJsonThread->wait(); - - delete screenshotData; - delete previewProjector; - delete studioProgramProjector; - delete previewProjectorSource; - delete previewProjectorMain; - delete sourceProjector; - delete sceneProjectorMenu; - delete scaleFilteringMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - delete perSceneTransitionMenu; - delete shortcutFilter; - delete trayMenu; - delete programOptions; - delete program; - - /* XXX: any obs data must be released before calling obs_shutdown. - * currently, we can't automate this with C++ RAII because of the - * delicate nature of obs_shutdown needing to be freed before the UI - * can be freed, and we have no control over the destruction order of - * the Qt UI stuff, so we have to manually clear any references to - * libobs. */ - delete cpuUsageTimer; - os_cpu_usage_info_destroy(cpuUsageInfo); - - obs_hotkey_set_callback_routing_func(nullptr, nullptr); - ClearHotkeys(); - - service = nullptr; - outputHandler.reset(); - - delete interaction; - delete properties; - delete filters; - delete transformWindow; - delete advAudioWindow; - delete about; - delete remux; - - obs_display_remove_draw_callback(ui->preview->GetDisplay(), OBSBasic::RenderMain, this); - - obs_enter_graphics(); - gs_vertexbuffer_destroy(box); - gs_vertexbuffer_destroy(boxLeft); - gs_vertexbuffer_destroy(boxTop); - gs_vertexbuffer_destroy(boxRight); - gs_vertexbuffer_destroy(boxBottom); - gs_vertexbuffer_destroy(circle); - gs_vertexbuffer_destroy(actionSafeMargin); - gs_vertexbuffer_destroy(graphicsSafeMargin); - gs_vertexbuffer_destroy(fourByThreeSafeMargin); - gs_vertexbuffer_destroy(leftLine); - gs_vertexbuffer_destroy(topLine); - gs_vertexbuffer_destroy(rightLine); - obs_leave_graphics(); - - /* When shutting down, sometimes source references can get in to the - * event queue, and if we don't forcibly process those events they - * won't get processed until after obs_shutdown has been called. I - * really wish there were a more elegant way to deal with this via C++, - * but Qt doesn't use C++ in a normal way, so you can't really rely on - * normal C++ behavior for your data to be freed in the order that you - * expect or want it to. */ - QApplication::sendPostedEvents(nullptr); - - config_set_int(App()->GetAppConfig(), "General", "LastVersion", LIBOBS_API_VER); - config_save_safe(App()->GetAppConfig(), "tmp", nullptr); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewEnabled", previewEnabled); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "AlwaysOnTop", ui->actionAlwaysOnTop->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SceneDuplicationMode", sceneDuplicationMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SwapScenesMode", swapScenesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "EditPropertiesMode", editPropertiesMode); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "PreviewProgramMode", IsPreviewProgramMode()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "DocksLocked", ui->lockDocks->isChecked()); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "SideDocks", ui->sideDocks->isChecked()); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - -#ifdef BROWSER_AVAILABLE - DestroyPanelCookieManager(); - delete cef; - cef = nullptr; -#endif -} - -void OBSBasic::SaveProjectNow() -{ - if (disableSaving) - return; - - projectChanged = true; - SaveProjectDeferred(); -} - -void OBSBasic::SaveProject() -{ - if (disableSaving) - return; - - projectChanged = true; - QMetaObject::invokeMethod(this, "SaveProjectDeferred", Qt::QueuedConnection); -} - -void OBSBasic::SaveProjectDeferred() -{ - if (disableSaving) - return; - - if (!projectChanged) - return; - - projectChanged = false; - - try { - const OBSSceneCollection ¤tCollection = GetCurrentSceneCollection(); - - Save(currentCollection.collectionFile.u8string().c_str()); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -OBSSource OBSBasic::GetProgramSource() -{ - return OBSGetStrongRef(programScene); -} - -OBSScene OBSBasic::GetCurrentScene() -{ - return currentScene.load(); -} - -OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item) -{ - return item ? GetOBSRef(item) : nullptr; -} - -OBSSceneItem OBSBasic::GetCurrentSceneItem() -{ - return ui->sources->Get(GetTopSelectedSourceItem()); -} - -void OBSBasic::UpdatePreviewScalingMenu() -{ - bool fixedScaling = ui->preview->IsFixedScaling(); - float scalingAmount = ui->preview->GetScalingAmount(); - if (!fixedScaling) { - ui->actionScaleWindow->setChecked(true); - ui->actionScaleCanvas->setChecked(false); - ui->actionScaleOutput->setChecked(false); - return; - } - - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->actionScaleWindow->setChecked(false); - ui->actionScaleCanvas->setChecked(scalingAmount == 1.0f); - ui->actionScaleOutput->setChecked(scalingAmount == float(ovi.output_width) / float(ovi.base_width)); -} - -void OBSBasic::CreateInteractionWindow(obs_source_t *source) -{ - bool closed = true; - if (interaction) - closed = interaction->close(); - - if (!closed) - return; - - interaction = new OBSBasicInteraction(this, source); - interaction->Init(); - interaction->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreatePropertiesWindow(obs_source_t *source) -{ - bool closed = true; - if (properties) - closed = properties->close(); - - if (!closed) - return; - - properties = new OBSBasicProperties(this, source); - properties->Init(); - properties->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::CreateFiltersWindow(obs_source_t *source) -{ - bool closed = true; - if (filters) - closed = filters->close(); - - if (!closed) - return; - - filters = new OBSBasicFilters(this, source); - filters->Init(); - filters->setAttribute(Qt::WA_DeleteOnClose, true); -} - -/* Qt callbacks for invokeMethod */ - -void OBSBasic::AddScene(OBSSource source) -{ - const char *name = obs_source_get_name(source); - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *item = new QListWidgetItem(QT_UTF8(name)); - SetOBSRef(item, OBSScene(scene)); - ui->scenes->insertItem(ui->scenes->currentRow() + 1, item); - - obs_hotkey_register_source( - source, "OBSBasic.SelectScene", Str("Basic.Hotkeys.SelectScene"), - [](void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed) { - OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); - - auto potential_source = static_cast(data); - OBSSourceAutoRelease source = obs_source_get_ref(potential_source); - if (source && pressed) - main->SetCurrentScene(source.Get()); - }, - static_cast(source)); - - signal_handler_t *handler = obs_source_get_signal_handler(source); - - SignalContainer container; - container.ref = scene; - container.handlers.assign({ - std::make_shared(handler, "item_add", OBSBasic::SceneItemAdded, this), - std::make_shared(handler, "reorder", OBSBasic::SceneReordered, this), - std::make_shared(handler, "refresh", OBSBasic::SceneRefreshed, this), - }); - - item->setData(static_cast(QtDataRole::OBSSignals), QVariant::fromValue(container)); - - /* if the scene already has items (a duplicated scene) add them */ - auto addSceneItem = [this](obs_sceneitem_t *item) { - AddSceneItem(item); - }; - - using addSceneItem_t = decltype(addSceneItem); - - obs_scene_enum_items( - scene, - [](obs_scene_t *, obs_sceneitem_t *item, void *param) { - addSceneItem_t *func; - func = reinterpret_cast(param); - (*func)(item); - return true; - }, - &addSceneItem); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *source = obs_scene_get_source(scene); - blog(LOG_INFO, "User added scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::RemoveScene(OBSSource source) -{ - obs_scene_t *scene = obs_scene_from_source(source); - - QListWidgetItem *sel = nullptr; - int count = ui->scenes->count(); - - for (int i = 0; i < count; i++) { - auto item = ui->scenes->item(i); - auto cur_scene = GetOBSRef(item); - if (cur_scene != scene) - continue; - - sel = item; - break; - } - - if (sel != nullptr) { - if (sel == ui->scenes->currentItem()) - ui->sources->Clear(); - delete sel; - } - - SaveProject(); - - if (!disableSaving) { - blog(LOG_INFO, "User Removed scene '%s'", obs_source_get_name(source)); - - OBSProjector::UpdateMultiviewProjectors(); - } - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_sceneitem_t *selectedItem = reinterpret_cast(param); - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, select_one, param); - - obs_sceneitem_select(item, (selectedItem == item)); - - return true; -} - -void OBSBasic::AddSceneItem(OBSSceneItem item) -{ - obs_scene_t *scene = obs_sceneitem_get_scene(item); - - if (GetCurrentScene() == scene) - ui->sources->Add(item); - - SaveProject(); - - if (!disableSaving) { - obs_source_t *sceneSource = obs_scene_get_source(scene); - obs_source_t *itemSource = obs_sceneitem_get_source(item); - blog(LOG_INFO, "User added source '%s' (%s) to scene '%s'", obs_source_get_name(itemSource), - obs_source_get_id(itemSource), obs_source_get_name(sceneSource)); - - obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item); - } -} - -static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName) -{ - QList items = listWidget->findItems(prevName, Qt::MatchExactly); - - for (int i = 0; i < items.count(); i++) - items[i]->setText(newName); -} - -void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName) -{ - RenameListValues(ui->scenes, newName, prevName); - - if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source)) - vcamConfig.source = newName.toStdString(); - if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene)) - vcamConfig.scene = newName.toStdString(); - - SaveProject(); - - obs_scene_t *scene = obs_scene_from_source(source); - if (scene) - OBSProjector::UpdateMultiviewProjectors(); - - UpdateContextBar(); - UpdatePreviewProgramIndicators(); -} - -void OBSBasic::ClearContextBar() -{ - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - delete la->widget(); - ui->emptySpace->layout()->removeItem(la); - } -} - -void OBSBasic::UpdateContextBarVisibility() -{ - int width = ui->centralwidget->size().width(); - - ContextBarSize contextBarSizeNew; - if (width >= 740) { - contextBarSizeNew = ContextBarSize_Normal; - } else if (width >= 600) { - contextBarSizeNew = ContextBarSize_Reduced; - } else { - contextBarSizeNew = ContextBarSize_Minimized; - } - - if (contextBarSize == contextBarSizeNew) - return; - - contextBarSize = contextBarSizeNew; - UpdateContextBarDeferred(); -} - -static bool is_network_media_source(obs_source_t *source, const char *id) -{ - if (strcmp(id, "ffmpeg_source") != 0) - return false; - - OBSDataAutoRelease s = obs_source_get_settings(source); - bool is_local_file = obs_data_get_bool(s, "is_local_file"); - - return !is_local_file; -} - -void OBSBasic::UpdateContextBarDeferred(bool force) -{ - QMetaObject::invokeMethod(this, "UpdateContextBar", Qt::QueuedConnection, Q_ARG(bool, force)); -} - -void OBSBasic::SourceToolBarActionsSetEnabled() -{ - bool enable = false; - bool disableProps = false; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - OBSSource source = obs_sceneitem_get_source(item); - disableProps = !obs_source_configurable(source); - - enable = true; - } - - if (disableProps) - ui->actionSourceProperties->setEnabled(false); - else - ui->actionSourceProperties->setEnabled(enable); - - ui->actionRemoveSource->setEnabled(enable); - ui->actionSourceUp->setEnabled(enable); - ui->actionSourceDown->setEnabled(enable); - - RefreshToolBarStyling(ui->sourcesToolbar); -} - -void OBSBasic::UpdateContextBar(bool force) -{ - SourceToolBarActionsSetEnabled(); - - if (!ui->contextContainer->isVisible() && !force) - return; - - OBSSceneItem item = GetCurrentSceneItem(); - - if (item) { - obs_source_t *source = obs_sceneitem_get_source(item); - - bool updateNeeded = true; - QLayoutItem *la = ui->emptySpace->layout()->itemAt(0); - if (la) { - if (SourceToolbar *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } else if (MediaControls *toolbar = dynamic_cast(la->widget())) { - if (toolbar->GetSource() == source) - updateNeeded = false; - } - } - - const char *id = obs_source_get_unversioned_id(source); - uint32_t flags = obs_source_get_output_flags(source); - - ui->sourceInteractButton->setVisible(flags & OBS_SOURCE_INTERACTION); - - if (contextBarSize >= ContextBarSize_Reduced && (updateNeeded || force)) { - ClearContextBar(); - if (flags & OBS_SOURCE_CONTROLLABLE_MEDIA) { - if (!is_network_media_source(source, id)) { - MediaControls *mediaControls = new MediaControls(ui->emptySpace); - mediaControls->SetSource(source); - - ui->emptySpace->layout()->addWidget(mediaControls); - } - } else if (strcmp(id, "browser_source") == 0) { - BrowserToolbar *c = new BrowserToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_input_capture") == 0 || - strcmp(id, "wasapi_output_capture") == 0 || - strcmp(id, "coreaudio_input_capture") == 0 || - strcmp(id, "coreaudio_output_capture") == 0 || - strcmp(id, "pulse_input_capture") == 0 || strcmp(id, "pulse_output_capture") == 0 || - strcmp(id, "alsa_input_capture") == 0) { - AudioCaptureToolbar *c = new AudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "wasapi_process_output_capture") == 0) { - ApplicationAudioCaptureToolbar *c = - new ApplicationAudioCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "window_capture") == 0 || strcmp(id, "xcomposite_input") == 0) { - WindowCaptureToolbar *c = new WindowCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "monitor_capture") == 0 || strcmp(id, "display_capture") == 0 || - strcmp(id, "xshm_input") == 0) { - DisplayCaptureToolbar *c = new DisplayCaptureToolbar(ui->emptySpace, source); - c->Init(); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "dshow_input") == 0) { - DeviceCaptureToolbar *c = new DeviceCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "game_capture") == 0) { - GameCaptureToolbar *c = new GameCaptureToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "image_source") == 0) { - ImageSourceToolbar *c = new ImageSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "color_source") == 0) { - ColorSourceToolbar *c = new ColorSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - - } else if (strcmp(id, "text_ft2_source") == 0 || strcmp(id, "text_gdiplus") == 0) { - TextSourceToolbar *c = new TextSourceToolbar(ui->emptySpace, source); - ui->emptySpace->layout()->addWidget(c); - } - } else if (contextBarSize == ContextBarSize_Minimized) { - ClearContextBar(); - } - - QIcon icon; - - if (strcmp(id, "scene") == 0) - icon = GetSceneIcon(); - else if (strcmp(id, "group") == 0) - icon = GetGroupIcon(); - else - icon = GetSourceIcon(id); - - QPixmap pixmap = icon.pixmap(QSize(16, 16)); - ui->contextSourceIcon->setPixmap(pixmap); - ui->contextSourceIconSpacer->hide(); - ui->contextSourceIcon->show(); - - const char *name = obs_source_get_name(source); - ui->contextSourceLabel->setText(name); - - ui->sourceFiltersButton->setEnabled(true); - ui->sourcePropertiesButton->setEnabled(obs_source_configurable(source)); - } else { - ClearContextBar(); - ui->contextSourceIcon->hide(); - ui->contextSourceIconSpacer->show(); - ui->contextSourceLabel->setText(QTStr("ContextBar.NoSelectedSource")); - - ui->sourceFiltersButton->setEnabled(false); - ui->sourcePropertiesButton->setEnabled(false); - ui->sourceInteractButton->setVisible(false); - } - - if (contextBarSize == ContextBarSize_Normal) { - ui->sourcePropertiesButton->setText(QTStr("Properties")); - ui->sourceFiltersButton->setText(QTStr("Filters")); - ui->sourceInteractButton->setText(QTStr("Interact")); - } else { - ui->sourcePropertiesButton->setText(""); - ui->sourceFiltersButton->setText(""); - ui->sourceInteractButton->setText(""); - } -} - -static inline bool SourceMixerHidden(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden"); - - return hidden; -} - -static inline void SetSourceMixerHidden(obs_source_t *source, bool hidden) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "mixer_hidden", hidden); -} - -void OBSBasic::GetAudioSourceFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreateFiltersWindow(source); -} - -void OBSBasic::GetAudioSourceProperties() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - CreatePropertiesWindow(source); -} - -void OBSBasic::HideAudioControl() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } -} - -void OBSBasic::UnhideAllAudioControls() -{ - auto UnhideAudioMixer = [this](obs_source_t *source) /* -- */ - { - if (!obs_source_active(source)) - return true; - if (!SourceMixerHidden(source)) - return true; - - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - return true; - }; - - using UnhideAudioMixer_t = decltype(UnhideAudioMixer); - - auto PreEnum = [](void *data, obs_source_t *source) -> bool /* -- */ - { - return (*reinterpret_cast(data))(source); - }; - - obs_enum_sources(PreEnum, &UnhideAudioMixer); -} - -void OBSBasic::ToggleHideMixer() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (!SourceMixerHidden(source)) { - SetSourceMixerHidden(source, true); - DeactivateAudioSource(source); - } else { - SetSourceMixerHidden(source, false); - ActivateAudioSource(source); - } -} - -void OBSBasic::MixerRenameSource() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - OBSSource source = vol->GetSource(); - - const char *prevName = obs_source_get_name(source); - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.MixerRename.Title"), - QTStr("Basic.Main.MixerRename.Text"), name, QT_UTF8(prevName)); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - OBSSourceAutoRelease sourceTest = obs_get_source_by_name(name.c_str()); - - if (sourceTest) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - obs_source_set_name(source, name.c_str()); - break; - } -} - -static inline bool SourceVolumeLocked(obs_source_t *source) -{ - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - bool lock = obs_data_get_bool(priv_settings, "volume_locked"); - - return lock; -} - -void OBSBasic::LockVolumeControl(bool lock) -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source); - obs_data_set_bool(priv_settings, "volume_locked", lock); - - vol->EnableSlider(!lock); -} - -void OBSBasic::VolControlContextMenu() -{ - VolControl *vol = reinterpret_cast(sender()); - - /* ------------------- */ - - QAction lockAction(QTStr("LockVolume"), this); - lockAction.setCheckable(true); - lockAction.setChecked(SourceVolumeLocked(vol->GetSource())); - - QAction hideAction(QTStr("Hide"), this); - QAction unhideAllAction(QTStr("UnhideAll"), this); - QAction mixerRenameAction(QTStr("Rename"), this); - - QAction copyFiltersAction(QTStr("Copy.Filters"), this); - QAction pasteFiltersAction(QTStr("Paste.Filters"), this); - - QAction filtersAction(QTStr("Filters"), this); - QAction propertiesAction(QTStr("Properties"), this); - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&hideAction, &QAction::triggered, this, &OBSBasic::HideAudioControl, Qt::DirectConnection); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - connect(&lockAction, &QAction::toggled, this, &OBSBasic::LockVolumeControl, Qt::DirectConnection); - connect(&mixerRenameAction, &QAction::triggered, this, &OBSBasic::MixerRenameSource, Qt::DirectConnection); - - connect(©FiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerCopyFilters, Qt::DirectConnection); - connect(&pasteFiltersAction, &QAction::triggered, this, &OBSBasic::AudioMixerPasteFilters, - Qt::DirectConnection); - - connect(&filtersAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceFilters, Qt::DirectConnection); - connect(&propertiesAction, &QAction::triggered, this, &OBSBasic::GetAudioSourceProperties, - Qt::DirectConnection); - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - hideAction.setProperty("volControl", QVariant::fromValue(vol)); - lockAction.setProperty("volControl", QVariant::fromValue(vol)); - mixerRenameAction.setProperty("volControl", QVariant::fromValue(vol)); - - copyFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - pasteFiltersAction.setProperty("volControl", QVariant::fromValue(vol)); - - filtersAction.setProperty("volControl", QVariant::fromValue(vol)); - propertiesAction.setProperty("volControl", QVariant::fromValue(vol)); - - /* ------------------- */ - - copyFiltersAction.setEnabled(obs_source_filter_count(vol->GetSource()) > 0); - pasteFiltersAction.setEnabled(!obs_weak_source_expired(copyFiltersSource)); - - QMenu popup; - vol->SetContextMenu(&popup); - popup.addAction(&lockAction); - popup.addSeparator(); - popup.addAction(&unhideAllAction); - popup.addAction(&hideAction); - popup.addAction(&mixerRenameAction); - popup.addSeparator(); - popup.addAction(©FiltersAction); - popup.addAction(&pasteFiltersAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&filtersAction); - popup.addAction(&propertiesAction); - popup.addAction(&advPropAction); - - // toggleControlLayoutAction deletes and re-creates the volume controls - // meaning that "vol" would be pointing to freed memory. - if (popup.exec(QCursor::pos()) != &toggleControlLayoutAction) - vol->SetContextMenu(nullptr); -} - -void OBSBasic::on_hMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::on_vMixerScrollArea_customContextMenuRequested() -{ - StackedMixerAreaContextMenuRequested(); -} - -void OBSBasic::StackedMixerAreaContextMenuRequested() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - - QAction advPropAction(QTStr("Basic.MainMenu.Edit.AdvAudio"), this); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - - /* ------------------- */ - - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - connect(&advPropAction, &QAction::triggered, this, &OBSBasic::on_actionAdvAudioProperties_triggered, - Qt::DirectConnection); - - /* ------------------- */ - - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - /* ------------------- */ - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.addSeparator(); - popup.addAction(&advPropAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::ToggleMixerLayout(bool vertical) -{ - if (vertical) { - ui->stackedMixerArea->setMinimumSize(180, 220); - ui->stackedMixerArea->setCurrentIndex(1); - } else { - ui->stackedMixerArea->setMinimumSize(220, 0); - ui->stackedMixerArea->setCurrentIndex(0); - } -} - -void OBSBasic::ToggleVolControlLayout() -{ - bool vertical = !config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl", vertical); - ToggleMixerLayout(vertical); - - // We need to store it so we can delete current and then add - // at the right order - vector sources; - for (size_t i = 0; i != volumes.size(); i++) - sources.emplace_back(volumes[i]->GetSource()); - - ClearVolumeControls(); - - for (const auto &source : sources) - ActivateAudioSource(source); -} - -void OBSBasic::ActivateAudioSource(OBSSource source) -{ - if (SourceMixerHidden(source)) - return; - if (!obs_source_active(source)) - return; - if (!obs_source_audio_active(source)) - return; - - bool vertical = config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl"); - VolControl *vol = new VolControl(source, true, vertical); - - vol->EnableSlider(!SourceVolumeLocked(source)); - - double meterDecayRate = config_get_double(activeConfiguration, "Audio", "MeterDecayRate"); - vol->SetMeterDecayRate(meterDecayRate); - - uint32_t peakMeterTypeIdx = config_get_uint(activeConfiguration, "Audio", "PeakMeterType"); - - enum obs_peak_meter_type peakMeterType; - switch (peakMeterTypeIdx) { - case 0: - peakMeterType = SAMPLE_PEAK_METER; - break; - case 1: - peakMeterType = TRUE_PEAK_METER; - break; - default: - peakMeterType = SAMPLE_PEAK_METER; - break; - } - - vol->setPeakMeterType(peakMeterType); - - vol->setContextMenuPolicy(Qt::CustomContextMenu); - - connect(vol, &QWidget::customContextMenuRequested, this, &OBSBasic::VolControlContextMenu); - connect(vol, &VolControl::ConfigClicked, this, &OBSBasic::VolControlContextMenu); - - InsertQObjectByName(volumes, vol); - - for (auto volume : volumes) { - if (vertical) - ui->vVolControlLayout->addWidget(volume); - else - ui->hVolControlLayout->addWidget(volume); - } -} - -void OBSBasic::DeactivateAudioSource(OBSSource source) -{ - for (size_t i = 0; i < volumes.size(); i++) { - if (volumes[i]->GetSource() == source) { - delete volumes[i]; - volumes.erase(volumes.begin() + i); - break; - } - } -} - -bool OBSBasic::QueryRemoveSource(obs_source_t *source) -{ - if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) { - int count = ui->scenes->count(); - - if (count == 1) { - OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text")); - return false; - } - } - - const char *name = obs_source_get_name(source); - - QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name)); - - QMessageBox remove_source(this); - remove_source.setText(text); - QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_source.setDefaultButton(Yes); - remove_source.addButton(QTStr("No"), QMessageBox::NoRole); - remove_source.setIcon(QMessageBox::Question); - remove_source.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_source.exec(); - - return Yes == remove_source.clickedButton(); -} - -#define UPDATE_CHECK_INTERVAL (60 * 60 * 24 * 4) /* 4 days */ - -void OBSBasic::TimedCheckForUpdates() -{ - if (App()->IsUpdaterDisabled()) - return; - if (!config_get_bool(App()->GetAppConfig(), "General", "EnableAutoUpdates")) - return; - -#if defined(ENABLE_SPARKLE_UPDATER) - CheckForUpdates(false); -#elif _WIN32 - long long lastUpdate = config_get_int(App()->GetAppConfig(), "General", "LastUpdateCheck"); - uint32_t lastVersion = config_get_int(App()->GetAppConfig(), "General", "LastVersion"); - - if (lastVersion < LIBOBS_API_VER) { - lastUpdate = 0; - config_set_int(App()->GetAppConfig(), "General", "LastUpdateCheck", 0); - } - - long long t = (long long)time(nullptr); - long long secs = t - lastUpdate; - - if (secs > UPDATE_CHECK_INTERVAL) - CheckForUpdates(false); -#endif -} - -void OBSBasic::CheckForUpdates(bool manualUpdate) -{ -#if _WIN32 - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - updateCheckThread.reset(new AutoUpdateThread(manualUpdate)); - updateCheckThread->start(); -#elif defined(ENABLE_SPARKLE_UPDATER) - ui->actionCheckForUpdates->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - MacUpdateThread *mut = new MacUpdateThread(manualUpdate); - connect(mut, &MacUpdateThread::Result, this, &OBSBasic::MacBranchesFetched, Qt::QueuedConnection); - updateCheckThread.reset(mut); - updateCheckThread->start(); -#else - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::MacBranchesFetched(const QString &branch, bool manualUpdate) -{ -#ifdef ENABLE_SPARKLE_UPDATER - static OBSSparkle *updater; - - if (!updater) { - updater = new OBSSparkle(QT_TO_UTF8(branch), ui->actionCheckForUpdates); - return; - } - - updater->setBranch(QT_TO_UTF8(branch)); - updater->checkForUpdates(manualUpdate); -#else - UNUSED_PARAMETER(branch); - UNUSED_PARAMETER(manualUpdate); -#endif -} - -void OBSBasic::updateCheckFinished() -{ - ui->actionCheckForUpdates->setEnabled(true); - ui->actionRepair->setEnabled(true); -} - -void OBSBasic::DuplicateSelectedScene() -{ - OBSScene curScene = GetCurrentScene(); - - if (!curScene) - return; - - OBSSource curSceneSource = obs_scene_get_source(curScene); - QString format{obs_source_get_name(curSceneSource)}; - format += " %1"; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - for (;;) { - string name; - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - if (!accepted) - return; - - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - obs_source_t *source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - obs_source_release(source); - continue; - } - - OBSSceneAutoRelease scene = obs_scene_duplicate(curScene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - - auto undo = [](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_source_remove(source); - }; - - auto redo = [this, name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_name(data.c_str()); - obs_scene_t *scene = obs_scene_from_source(source); - scene = obs_scene_duplicate(scene, name.c_str(), OBS_SCENE_DUP_REFS); - source = obs_scene_get_source(scene); - SetCurrentScene(source.Get(), true); - }; - - undo_s.add_action(QTStr("Undo.Scene.Duplicate").arg(obs_source_get_name(source)), undo, redo, - obs_source_get_name(source), obs_source_get_name(obs_scene_get_source(curScene))); - - break; - } -} - -static bool save_undo_source_enum(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *p) -{ - obs_source_t *source = obs_sceneitem_get_source(item); - if (obs_obj_is_private(source) && !obs_source_removed(source)) - return true; - - obs_data_array_t *array = (obs_data_array_t *)p; - - /* check if the source is already stored in the array */ - const char *name = obs_source_get_name(source); - const size_t count = obs_data_array_count(array); - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease sourceData = obs_data_array_item(array, i); - if (strcmp(name, obs_data_get_string(sourceData, "name")) == 0) - return true; - } - - if (obs_source_is_group(source)) - obs_scene_enum_items(obs_group_from_source(source), save_undo_source_enum, p); - - OBSDataAutoRelease source_data = obs_save_source(source); - obs_data_array_push_back(array, source_data); - return true; -} - -static inline void RemoveSceneAndReleaseNested(obs_source_t *source) -{ - obs_source_remove(source); - auto cb = [](void *, obs_source_t *source) { - if (strcmp(obs_source_get_id(source), "scene") == 0) - obs_scene_prune_sources(obs_scene_from_source(source)); - return true; - }; - obs_enum_scenes(cb, NULL); -} - -void OBSBasic::RemoveSelectedScene() -{ - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (!source || !QueryRemoveSource(source)) { - return; - } - - /* ------------------------------ */ - /* save all sources in scene */ - - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_array_create(); - - obs_scene_enum_items(scene, save_undo_source_enum, sources_in_deleted_scene); - - OBSDataAutoRelease scene_data = obs_save_source(source); - obs_data_array_push_back(sources_in_deleted_scene, scene_data); - - /* ----------------------------------------------- */ - /* save all scenes and groups the scene is used in */ - - OBSDataArrayAutoRelease scene_used_in_other_scenes = obs_data_array_create(); - - struct other_scenes_cb_data { - obs_source_t *oldScene; - obs_data_array_t *scene_used_in_other_scenes; - } other_scenes_cb_data; - other_scenes_cb_data.oldScene = source; - other_scenes_cb_data.scene_used_in_other_scenes = scene_used_in_other_scenes; - - auto other_scenes_cb = [](void *data_ptr, obs_source_t *scene) { - struct other_scenes_cb_data *data = (struct other_scenes_cb_data *)data_ptr; - if (strcmp(obs_source_get_name(scene), obs_source_get_name(data->oldScene)) == 0) - return true; - obs_sceneitem_t *item = obs_scene_find_source(obs_group_or_scene_from_source(scene), - obs_source_get_name(data->oldScene)); - if (item) { - OBSDataAutoRelease scene_data = - obs_save_source(obs_scene_get_source(obs_sceneitem_get_scene(item))); - obs_data_array_push_back(data->scene_used_in_other_scenes, scene_data); - } - return true; - }; - obs_enum_scenes(other_scenes_cb, &other_scenes_cb_data); - - /* --------------------------- */ - /* undo/redo */ - - auto undo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease sources_in_deleted_scene = obs_data_get_array(base, "sources_in_deleted_scene"); - OBSDataArrayAutoRelease scene_used_in_other_scenes = - obs_data_get_array(base, "scene_used_in_other_scenes"); - int savedIndex = (int)obs_data_get_int(base, "index"); - std::vector sources; - - /* create missing sources */ - size_t count = obs_data_array_count(sources_in_deleted_scene); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(sources_in_deleted_scene, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) { - source = obs_load_source(data); - sources.push_back(source.Get()); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - /* Add scene to scenes and groups it was nested in */ - for (size_t i = 0; i < obs_data_array_count(scene_used_in_other_scenes); i++) { - OBSDataAutoRelease data = obs_data_array_item(scene_used_in_other_scenes, i); - const char *name = obs_data_get_string(data, "name"); - OBSSourceAutoRelease source = obs_get_source_by_name(name); - - OBSDataAutoRelease settings = obs_data_get_obj(data, "settings"); - OBSDataArrayAutoRelease items = obs_data_get_array(settings, "items"); - - /* Clear scene, but keep a reference to all sources in the scene to make sure they don't get destroyed */ - std::vector existing_sources; - auto cb = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - std::vector *existing = (std::vector *)data; - OBSSource source = obs_sceneitem_get_source(item); - obs_sceneitem_remove(item); - existing->push_back(source); - return true; - }; - obs_scene_enum_items(obs_group_or_scene_from_source(source), cb, (void *)&existing_sources); - - /* Re-add sources to the scene */ - obs_sceneitems_add(obs_group_or_scene_from_source(source), items); - } - - obs_source_t *scene_source = sources.back(); - OBSScene scene = obs_scene_from_source(scene_source); - SetCurrentScene(scene, true); - - /* set original index in list box */ - ui->scenes->blockSignals(true); - int curIndex = ui->scenes->currentRow(); - QListWidgetItem *item = ui->scenes->takeItem(curIndex); - ui->scenes->insertItem(savedIndex, item); - ui->scenes->setCurrentRow(savedIndex); - currentScene = scene.Get(); - ui->scenes->blockSignals(false); - }; - - auto redo = [](const std::string &name) { - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - RemoveSceneAndReleaseNested(source); - }; - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_array(data, "sources_in_deleted_scene", sources_in_deleted_scene); - obs_data_set_array(data, "scene_used_in_other_scenes", scene_used_in_other_scenes); - obs_data_set_int(data, "index", ui->scenes->currentRow()); - - const char *scene_name = obs_source_get_name(source); - undo_s.add_action(QTStr("Undo.Delete").arg(scene_name), undo, redo, obs_data_get_json(data), scene_name); - - /* --------------------------- */ - /* remove */ - - RemoveSceneAndReleaseNested(source); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::ReorderSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->ReorderItems(); - SaveProject(); -} - -void OBSBasic::RefreshSources(OBSScene scene) -{ - if (scene != GetCurrentScene() || ui->sources->IgnoreReorder()) - return; - - ui->sources->RefreshItems(); - SaveProject(); -} - -/* OBS Callbacks */ - -void OBSBasic::SceneReordered(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "ReorderSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneRefreshed(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_scene_t *scene = (obs_scene_t *)calldata_ptr(params, "scene"); - - QMetaObject::invokeMethod(window, "RefreshSources", Q_ARG(OBSScene, OBSScene(scene))); -} - -void OBSBasic::SceneItemAdded(void *data, calldata_t *params) -{ - OBSBasic *window = static_cast(data); - - obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(params, "item"); - - QMetaObject::invokeMethod(window, "AddSceneItem", Q_ARG(OBSSceneItem, OBSSceneItem(item))); -} - -void OBSBasic::SourceCreated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "AddScene", WaitConnection(), - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRemoved(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_scene_from_source(source) != NULL) - QMetaObject::invokeMethod(static_cast(data), "RemoveScene", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - uint32_t flags = obs_source_get_output_flags(source); - - if (flags & OBS_SOURCE_AUDIO) - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioActivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - - if (obs_source_active(source)) - QMetaObject::invokeMethod(static_cast(data), "ActivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceAudioDeactivated(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - QMetaObject::invokeMethod(static_cast(data), "DeactivateAudioSource", - Q_ARG(OBSSource, OBSSource(source))); -} - -void OBSBasic::SourceRenamed(void *data, calldata_t *params) -{ - obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source"); - const char *newName = calldata_string(params, "new_name"); - const char *prevName = calldata_string(params, "prev_name"); - - QMetaObject::invokeMethod(static_cast(data), "RenameSources", Q_ARG(OBSSource, source), - Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName))); - - blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName); -} - -void OBSBasic::DrawBackdrop(float cx, float cy) -{ - if (!box) - return; - - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawBackdrop"); - - gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID); - gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color"); - gs_technique_t *tech = gs_effect_get_technique(solid, "Solid"); - - vec4 colorVal; - vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); - gs_effect_set_vec4(color, &colorVal); - - gs_technique_begin(tech); - gs_technique_begin_pass(tech, 0); - gs_matrix_push(); - gs_matrix_identity(); - gs_matrix_scale3f(float(cx), float(cy), 1.0f); - - gs_load_vertexbuffer(box); - gs_draw(GS_TRISTRIP, 0, 0); - - gs_matrix_pop(); - gs_technique_end_pass(tech); - gs_technique_end(tech); - - gs_load_vertexbuffer(nullptr); - - GS_DEBUG_MARKER_END(); -} - -void OBSBasic::RenderMain(void *data, uint32_t, uint32_t) -{ - GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "RenderMain"); - - OBSBasic *window = static_cast(data); - obs_video_info ovi; - - obs_get_video_info(&ovi); - - window->previewCX = int(window->previewScale * float(ovi.base_width)); - window->previewCY = int(window->previewScale * float(ovi.base_height)); - - gs_viewport_push(); - gs_projection_push(); - - obs_display_t *display = window->ui->preview->GetDisplay(); - uint32_t width, height; - obs_display_size(display, &width, &height); - float right = float(width) - window->previewX; - float bottom = float(height) - window->previewY; - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - - window->ui->preview->DrawOverflow(); - - /* --------------------------------------- */ - - gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_set_viewport(window->previewX, window->previewY, window->previewCX, window->previewCY); - - if (window->IsPreviewProgramMode()) { - window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); - - OBSScene scene = window->GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - if (source) - obs_source_video_render(source); - } else { - obs_render_main_texture_src_color_only(); - } - gs_load_vertexbuffer(nullptr); - - /* --------------------------------------- */ - - gs_ortho(-window->previewX, right, -window->previewY, bottom, -100.0f, 100.0f); - gs_reset_viewport(); - - uint32_t targetCX = window->previewCX; - uint32_t targetCY = window->previewCY; - - if (window->drawSafeAreas) { - RenderSafeAreas(window->actionSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->graphicsSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->fourByThreeSafeMargin, targetCX, targetCY); - RenderSafeAreas(window->leftLine, targetCX, targetCY); - RenderSafeAreas(window->topLine, targetCX, targetCY); - RenderSafeAreas(window->rightLine, targetCX, targetCY); - } - - window->ui->preview->DrawSceneEditing(); - - if (window->drawSpacingHelpers) - window->ui->preview->DrawSpacingHelpers(); - - /* --------------------------------------- */ - - gs_projection_pop(); - gs_viewport_pop(); - - GS_DEBUG_MARKER_END(); -} - -/* Main class functions */ - -obs_service_t *OBSBasic::GetService() -{ - if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL, nullptr); - obs_service_release(service); - } - return service; -} - -void OBSBasic::SetService(obs_service_t *newService) -{ - if (newService) { - service = newService; - } -} - -int OBSBasic::GetTransitionDuration() -{ - return ui->transitionDuration->value(); -} - -bool OBSBasic::Active() const -{ - if (!outputHandler) - return false; - return outputHandler->Active(); -} - -#ifdef _WIN32 -#define IS_WIN32 1 -#else -#define IS_WIN32 0 -#endif - -static inline int AttemptToResetVideo(struct obs_video_info *ovi) -{ - return obs_reset_video(ovi); -} - -static inline enum obs_scale_type GetScaleType(ConfigFile &activeConfiguration) -{ - const char *scaleTypeStr = config_get_string(activeConfiguration, "Video", "ScaleType"); - - if (astrcmpi(scaleTypeStr, "bilinear") == 0) - return OBS_SCALE_BILINEAR; - else if (astrcmpi(scaleTypeStr, "lanczos") == 0) - return OBS_SCALE_LANCZOS; - else if (astrcmpi(scaleTypeStr, "area") == 0) - return OBS_SCALE_AREA; - else - return OBS_SCALE_BICUBIC; -} - -static inline enum video_format GetVideoFormatFromName(const char *name) -{ - if (astrcmpi(name, "I420") == 0) - return VIDEO_FORMAT_I420; - else if (astrcmpi(name, "NV12") == 0) - return VIDEO_FORMAT_NV12; - else if (astrcmpi(name, "I444") == 0) - return VIDEO_FORMAT_I444; - else if (astrcmpi(name, "I010") == 0) - return VIDEO_FORMAT_I010; - else if (astrcmpi(name, "P010") == 0) - return VIDEO_FORMAT_P010; - else if (astrcmpi(name, "P216") == 0) - return VIDEO_FORMAT_P216; - else if (astrcmpi(name, "P416") == 0) - return VIDEO_FORMAT_P416; -#if 0 //currently unsupported - else if (astrcmpi(name, "YVYU") == 0) - return VIDEO_FORMAT_YVYU; - else if (astrcmpi(name, "YUY2") == 0) - return VIDEO_FORMAT_YUY2; - else if (astrcmpi(name, "UYVY") == 0) - return VIDEO_FORMAT_UYVY; -#endif - else - return VIDEO_FORMAT_BGRA; -} - -static inline enum video_colorspace GetVideoColorSpaceFromName(const char *name) -{ - enum video_colorspace colorspace = VIDEO_CS_SRGB; - if (strcmp(name, "601") == 0) - colorspace = VIDEO_CS_601; - else if (strcmp(name, "709") == 0) - colorspace = VIDEO_CS_709; - else if (strcmp(name, "2100PQ") == 0) - colorspace = VIDEO_CS_2100_PQ; - else if (strcmp(name, "2100HLG") == 0) - colorspace = VIDEO_CS_2100_HLG; - - return colorspace; -} - -void OBSBasic::ResetUI() -{ - bool studioPortraitLayout = config_get_bool(App()->GetUserConfig(), "BasicWindow", "StudioPortraitLayout"); - - if (studioPortraitLayout) - ui->previewLayout->setDirection(QBoxLayout::BottomToTop); - else - ui->previewLayout->setDirection(QBoxLayout::LeftToRight); - - UpdatePreviewProgramIndicators(); -} - -int OBSBasic::ResetVideo() -{ - if (outputHandler && outputHandler->Active()) - return OBS_VIDEO_CURRENTLY_ACTIVE; - - ProfileScope("OBSBasic::ResetVideo"); - - struct obs_video_info ovi; - int ret; - - GetConfigFPS(ovi.fps_num, ovi.fps_den); - - const char *colorFormat = config_get_string(activeConfiguration, "Video", "ColorFormat"); - const char *colorSpace = config_get_string(activeConfiguration, "Video", "ColorSpace"); - const char *colorRange = config_get_string(activeConfiguration, "Video", "ColorRange"); - - ovi.graphics_module = App()->GetRenderModule(); - ovi.base_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCX"); - ovi.base_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "BaseCY"); - ovi.output_width = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCX"); - ovi.output_height = (uint32_t)config_get_uint(activeConfiguration, "Video", "OutputCY"); - ovi.output_format = GetVideoFormatFromName(colorFormat); - ovi.colorspace = GetVideoColorSpaceFromName(colorSpace); - ovi.range = astrcmpi(colorRange, "Full") == 0 ? VIDEO_RANGE_FULL : VIDEO_RANGE_PARTIAL; - ovi.adapter = config_get_uint(App()->GetUserConfig(), "Video", "AdapterIdx"); - ovi.gpu_conversion = true; - ovi.scale_type = GetScaleType(activeConfiguration); - - if (ovi.base_width < 32 || ovi.base_height < 32) { - ovi.base_width = 1920; - ovi.base_height = 1080; - config_set_uint(activeConfiguration, "Video", "BaseCX", 1920); - config_set_uint(activeConfiguration, "Video", "BaseCY", 1080); - } - - if (ovi.output_width < 32 || ovi.output_height < 32) { - ovi.output_width = ovi.base_width; - ovi.output_height = ovi.base_height; - config_set_uint(activeConfiguration, "Video", "OutputCX", ovi.base_width); - config_set_uint(activeConfiguration, "Video", "OutputCY", ovi.base_height); - } - - ret = AttemptToResetVideo(&ovi); - if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) { - blog(LOG_WARNING, "Tried to reset when already active"); - return ret; - } - - if (ret == OBS_VIDEO_SUCCESS) { - ResizePreview(ovi.base_width, ovi.base_height); - if (program) - ResizeProgram(ovi.base_width, ovi.base_height); - - const float sdr_white_level = (float)config_get_uint(activeConfiguration, "Video", "SdrWhiteLevel"); - const float hdr_nominal_peak_level = - (float)config_get_uint(activeConfiguration, "Video", "HdrNominalPeakLevel"); - obs_set_video_levels(sdr_white_level, hdr_nominal_peak_level); - OBSBasicStats::InitializeValues(); - OBSProjector::UpdateMultiviewProjectors(); - - bool canMigrate = usingAbsoluteCoordinates || - (migrationBaseResolution && (migrationBaseResolution->first != ovi.base_width || - migrationBaseResolution->second != ovi.base_height)); - ui->actionRemigrateSceneCollection->setEnabled(canMigrate); - - emit CanvasResized(ovi.base_width, ovi.base_height); - emit OutputResized(ovi.output_width, ovi.output_height); - } - - return ret; -} - -bool OBSBasic::ResetAudio() -{ - ProfileScope("OBSBasic::ResetAudio"); - - struct obs_audio_info2 ai = {}; - ai.samples_per_sec = config_get_uint(activeConfiguration, "Audio", "SampleRate"); - - const char *channelSetupStr = config_get_string(activeConfiguration, "Audio", "ChannelSetup"); - - if (strcmp(channelSetupStr, "Mono") == 0) - ai.speakers = SPEAKERS_MONO; - else if (strcmp(channelSetupStr, "2.1") == 0) - ai.speakers = SPEAKERS_2POINT1; - else if (strcmp(channelSetupStr, "4.0") == 0) - ai.speakers = SPEAKERS_4POINT0; - else if (strcmp(channelSetupStr, "4.1") == 0) - ai.speakers = SPEAKERS_4POINT1; - else if (strcmp(channelSetupStr, "5.1") == 0) - ai.speakers = SPEAKERS_5POINT1; - else if (strcmp(channelSetupStr, "7.1") == 0) - ai.speakers = SPEAKERS_7POINT1; - else - ai.speakers = SPEAKERS_STEREO; - - bool lowLatencyAudioBuffering = config_get_bool(App()->GetUserConfig(), "Audio", "LowLatencyAudioBuffering"); - if (lowLatencyAudioBuffering) { - ai.max_buffering_ms = 20; - ai.fixed_buffering = true; - } - - return obs_reset_audio2(&ai); -} - -extern char *get_new_source_name(const char *name, const char *format); - -void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel) -{ - bool disable = deviceId && strcmp(deviceId, "disabled") == 0; - OBSSourceAutoRelease source; - OBSDataAutoRelease settings; - - source = obs_get_output_source(channel); - if (source) { - if (disable) { - obs_set_output_source(channel, nullptr); - } else { - settings = obs_source_get_settings(source); - const char *oldId = obs_data_get_string(settings, "device_id"); - if (strcmp(oldId, deviceId) != 0) { - obs_data_set_string(settings, "device_id", deviceId); - obs_source_update(source, settings); - } - } - - } else if (!disable) { - BPtr name = get_new_source_name(deviceDesc, "%s (%d)"); - - settings = obs_data_create(); - obs_data_set_string(settings, "device_id", deviceId); - source = obs_source_create(sourceId, name, settings, nullptr); - - obs_set_output_source(channel, source); - } -} - -void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) -{ - QSize targetSize; - bool isFixedScaling; - obs_video_info ovi; - - /* resize preview panel to fix to the top section of the window */ - targetSize = GetPixelSize(ui->preview); - - isFixedScaling = ui->preview->IsFixedScaling(); - obs_get_video_info(&ovi); - - if (isFixedScaling) { - previewScale = ui->preview->GetScalingAmount(); - - ui->preview->ClampScrollingOffsets(); - - GetCenterPosFromFixedScale(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, - previewScale); - previewX += ui->preview->GetScrollX(); - previewY += ui->preview->GetScrollY(); - - } else { - GetScaleAndCenterPos(int(cx), int(cy), targetSize.width() - PREVIEW_EDGE_SIZE * 2, - targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); - } - - ui->preview->SetScalingAmount(previewScale); - - previewX += float(PREVIEW_EDGE_SIZE); - previewY += float(PREVIEW_EDGE_SIZE); -} - -void OBSBasic::CloseDialogs() -{ - QList childDialogs = this->findChildren(); - if (!childDialogs.isEmpty()) { - for (int i = 0; i < childDialogs.size(); ++i) { - childDialogs.at(i)->close(); - } - } - - if (!stats.isNull()) - stats->close(); //call close to save Stats geometry - if (!remux.isNull()) - remux->close(); -} - -void OBSBasic::EnumDialogs() -{ - visDialogs.clear(); - modalDialogs.clear(); - visMsgBoxes.clear(); - - /* fill list of Visible dialogs and Modal dialogs */ - QList dialogs = findChildren(); - for (QDialog *dialog : dialogs) { - if (dialog->isVisible()) - visDialogs.append(dialog); - if (dialog->isModal()) - modalDialogs.append(dialog); - } - - /* fill list of Visible message boxes */ - QList msgBoxes = findChildren(); - for (QMessageBox *msgbox : msgBoxes) { - if (msgbox->isVisible()) - visMsgBoxes.append(msgbox); - } -} - -void OBSBasic::ClearProjectors() -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i]) - delete projectors[i]; - } - - projectors.clear(); -} - -void OBSBasic::ClearSceneData() -{ - disableSaving++; - - setCursor(Qt::WaitCursor); - - CloseDialogs(); - - ClearVolumeControls(); - ClearListItems(ui->scenes); - ui->sources->Clear(); - ClearQuickTransitions(); - ui->transitions->clear(); - - ClearProjectors(); - - for (int i = 0; i < MAX_CHANNELS; i++) - obs_set_output_source(i, nullptr); - - /* Reset VCam to default to clear its private scene and any references - * it holds. It will be reconfigured during loading. */ - if (vcamEnabled) { - vcamConfig.type = VCamOutputType::ProgramView; - outputHandler->UpdateVirtualCamOutputSource(); - } - - collectionModuleData = nullptr; - lastScene = nullptr; - swapScene = nullptr; - programScene = nullptr; - prevFTBSource = nullptr; - - clipboard.clear(); - copyFiltersSource = nullptr; - copyFilter = nullptr; - - auto cb = [](void *, obs_source_t *source) { - obs_source_remove(source); - return true; - }; - - obs_enum_scenes(cb, nullptr); - obs_enum_sources(cb, nullptr); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP); - - undo_s.clear(); - - /* using QEvent::DeferredDelete explicitly is the only way to ensure - * that deleteLater events are processed at this point */ - QApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); - - do { - QApplication::sendPostedEvents(nullptr); - } while (obs_wait_for_destroy_queue()); - - /* Pump Qt events one final time to give remaining signals time to be - * processed (since this happens after the destroy thread finishes and - * the audio/video threads have processed their tasks). */ - QApplication::sendPostedEvents(nullptr); - - unsetCursor(); - - /* If scene data wasn't actually cleared, e.g. faulty plugin holding a - * reference, they will still be in the hash table, enumerate them and - * store the names for logging purposes. */ - auto cb2 = [](void *param, obs_source_t *source) { - auto orphans = static_cast *>(param); - orphans->push_back(obs_source_get_name(source)); - return true; - }; - - vector orphan_sources; - obs_enum_sources(cb2, &orphan_sources); - - if (!orphan_sources.empty()) { - /* Avoid logging list twice in case it gets called after - * setting the flag the first time. */ - if (!clearingFailed) { - /* This ugly mess exists to join a vector of strings - * with a user-defined delimiter. */ - string orphan_names = - std::accumulate(orphan_sources.begin(), orphan_sources.end(), string(""), - [](string a, string b) { return std::move(a) + "\n- " + b; }); - - blog(LOG_ERROR, "Not all sources were cleared when clearing scene data:\n%s\n", - orphan_names.c_str()); - } - - /* We do not decrement disableSaving here to avoid OBS - * overwriting user data with garbage. */ - clearingFailed = true; - } else { - disableSaving--; - - blog(LOG_INFO, "All scene data cleared"); - blog(LOG_INFO, "------------------------------------------------"); - } -} - -void OBSBasic::closeEvent(QCloseEvent *event) -{ - /* Wait for multitrack video stream to start/finish processing in the background */ - if (setupStreamingGuard.valid() && - setupStreamingGuard.wait_for(std::chrono::seconds{0}) != std::future_status::ready) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - - /* Do not close window if inside of a temporary event loop because we - * could be inside of an Auth::LoadUI call. Keep trying once per - * second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } - -#ifdef YOUTUBE_ENABLED - /* Also don't close the window if the youtube stream check is active */ - if (youtubeStreamCheckThread) { - QTimer::singleShot(1000, this, &OBSBasic::close); - event->ignore(); - return; - } -#endif - - if (isVisible()) - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - bool confirmOnExit = config_get_bool(App()->GetUserConfig(), "General", "ConfirmOnExit"); - - if (confirmOnExit && outputHandler && outputHandler->Active() && !clearingFailed) { - SetShowing(true); - - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmExit.Title"), QTStr("ConfirmExit.Text")); - - if (button == QMessageBox::No) { - event->ignore(); - restart = false; - return; - } - } - - if (remux && !remux->close()) { - event->ignore(); - restart = false; - return; - } - - QWidget::closeEvent(event); - if (!event->isAccepted()) - return; - - blog(LOG_INFO, SHUTDOWN_SEPARATOR); - - closing = true; - - /* While closing, a resize event to OBSQTDisplay could be triggered. - * The graphics thread on macOS dispatches a lambda function to be - * executed asynchronously in the main thread. However, the display is - * sometimes deleted before the lambda function is actually executed. - * To avoid such a case, destroy displays earlier than others such as - * deleting browser docks. */ - ui->preview->DestroyDisplay(); - if (program) - program->DestroyDisplay(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - if (introCheckThread) - introCheckThread->wait(); - if (whatsNewInitThread) - whatsNewInitThread->wait(); - if (updateCheckThread) - updateCheckThread->wait(); - if (logUploadThread) - logUploadThread->wait(); - if (devicePropertiesThread && devicePropertiesThread->isRunning()) { - devicePropertiesThread->wait(); - devicePropertiesThread.reset(); - } - - QApplication::sendPostedEvents(nullptr); - - signalHandlers.clear(); - - Auth::Save(); - SaveProjectNow(); - auth.reset(); - - delete extraBrowsers; - - config_set_string(App()->GetUserConfig(), "BasicWindow", "DockState", saveState().toBase64().constData()); - -#ifdef BROWSER_AVAILABLE - if (cef) - SaveExtraBrowserDocks(); - - ClearExtraBrowserDocks(); -#endif - - OnEvent(OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN); - - disableSaving++; - - /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, - * sources, etc) so that all references are released before shutdown */ - ClearSceneData(); - - OnEvent(OBS_FRONTEND_EVENT_EXIT); - - // Destroys the frontend API so plugins can't continue calling it - obs_frontend_set_callbacks_internal(nullptr); - api = nullptr; - - QMetaObject::invokeMethod(App(), "quit", Qt::QueuedConnection); -} - -bool OBSBasic::nativeEvent(const QByteArray &, void *message, qintptr *) -{ -#ifdef _WIN32 - const MSG &msg = *static_cast(message); - switch (msg.message) { - case WM_MOVE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnMove(); - } - break; - case WM_DISPLAYCHANGE: - for (OBSQTDisplay *const display : findChildren()) { - display->OnDisplayChange(); - } - } -#else - UNUSED_PARAMETER(message); -#endif - - return false; -} - -void OBSBasic::changeEvent(QEvent *event) -{ - if (event->type() == QEvent::WindowStateChange) { - QWindowStateChangeEvent *stateEvent = (QWindowStateChangeEvent *)event; - - if (isMinimized()) { - if (trayIcon && trayIcon->isVisible() && sysTrayMinimizeToTray()) { - ToggleShowHide(); - return; - } - - if (previewEnabled) - EnablePreviewDisplay(false); - } else if (stateEvent->oldState() & Qt::WindowMinimized && isVisible()) { - if (previewEnabled) - EnablePreviewDisplay(true); - } - } -} - -void OBSBasic::on_actionShow_Recordings_triggered() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *type = config_get_string(activeConfiguration, "AdvOut", "RecType"); - const char *adv_path = strcmp(type, "Standard") - ? config_get_string(activeConfiguration, "AdvOut", "FFFilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : adv_path; - QDesktopServices::openUrl(QUrl::fromLocalFile(path)); -} - -void OBSBasic::on_actionRemux_triggered() -{ - if (!remux.isNull()) { - remux->show(); - remux->raise(); - return; - } - - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - const char *path = strcmp(mode, "Advanced") ? config_get_string(activeConfiguration, "SimpleOutput", "FilePath") - : config_get_string(activeConfiguration, "AdvOut", "RecFilePath"); - - OBSRemux *remuxDlg; - remuxDlg = new OBSRemux(path, this); - remuxDlg->show(); - remux = remuxDlg; -} - -void OBSBasic::on_action_Settings_triggered() -{ - static bool settings_already_executing = false; - - /* Do not load settings window if inside of a temporary event loop - * because we could be inside of an Auth::LoadUI call. Keep trying - * once per second until we've exit any known sub-loops. */ - if (os_atomic_load_long(&insideEventLoop) != 0) { - QTimer::singleShot(1000, this, &OBSBasic::on_action_Settings_triggered); - return; - } - - if (settings_already_executing) { - return; - } - - settings_already_executing = true; - - { - OBSBasicSettings settings(this); - settings.exec(); - } - - settings_already_executing = false; - - if (restart) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("Restart"), QTStr("NeedsRestart")); - - if (button == QMessageBox::Yes) - close(); - else - restart = false; - } -} - -void OBSBasic::on_actionShowMacPermissions_triggered() -{ -#ifdef __APPLE__ - OBSPermissions check(this, CheckPermission(kScreenCapture), CheckPermission(kVideoDeviceAccess), - CheckPermission(kAudioDeviceAccess), CheckPermission(kAccessibility)); - check.exec(); -#endif -} - -void OBSBasic::ShowMissingFilesDialog(obs_missing_files_t *files) -{ - if (obs_missing_files_count(files) > 0) { - /* When loading the missing files dialog on launch, the - * window hasn't fully initialized by this point on macOS, - * so put this at the end of the current task queue. Fixes - * a bug where the window is behind OBS on startup. */ - QTimer::singleShot(0, [this, files] { - missDialog = new OBSMissingFiles(files, this); - missDialog->setAttribute(Qt::WA_DeleteOnClose, true); - missDialog->show(); - missDialog->raise(); - }); - } else { - obs_missing_files_destroy(files); - - /* Only raise dialog if triggered manually */ - if (!disableSaving) - OBSMessageBox::information(this, QTStr("MissingFiles.NoMissing.Title"), - QTStr("MissingFiles.NoMissing.Text")); - } -} - -void OBSBasic::on_actionShowMissingFiles_triggered() -{ - obs_missing_files_t *files = obs_missing_files_create(); - - auto cb_sources = [](void *data, obs_source_t *source) { - AddMissingFiles(data, source); - return true; - }; - - obs_enum_all_sources(cb_sources, files); - ShowMissingFilesDialog(files); -} - -void OBSBasic::on_actionAdvAudioProperties_triggered() -{ - if (advAudioWindow != nullptr) { - advAudioWindow->raise(); - return; - } - - bool iconsVisible = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons"); - - advAudioWindow = new OBSBasicAdvAudio(this); - advAudioWindow->show(); - advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true); - advAudioWindow->SetIconsVisible(iconsVisible); -} - -void OBSBasic::on_actionMixerToolbarAdvAudio_triggered() -{ - on_actionAdvAudioProperties_triggered(); -} - -void OBSBasic::on_actionMixerToolbarMenu_triggered() -{ - QAction unhideAllAction(QTStr("UnhideAll"), this); - connect(&unhideAllAction, &QAction::triggered, this, &OBSBasic::UnhideAllAudioControls, Qt::DirectConnection); - - QAction toggleControlLayoutAction(QTStr("VerticalLayout"), this); - toggleControlLayoutAction.setCheckable(true); - toggleControlLayoutAction.setChecked( - config_get_bool(App()->GetUserConfig(), "BasicWindow", "VerticalVolControl")); - connect(&toggleControlLayoutAction, &QAction::changed, this, &OBSBasic::ToggleVolControlLayout, - Qt::DirectConnection); - - QMenu popup; - popup.addAction(&unhideAllAction); - popup.addSeparator(); - popup.addAction(&toggleControlLayoutAction); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *) -{ - OBSSource source; - - if (current) { - OBSScene scene = GetOBSRef(current); - source = obs_scene_get_source(scene); - - currentScene = scene; - } else { - currentScene = NULL; - } - - SetCurrentScene(source); - - if (vcamEnabled && vcamConfig.type == VCamOutputType::PreviewOutput) - outputHandler->UpdateVirtualCamOutputSource(); - - OnEvent(OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED); - - UpdateContextBar(); -} - -void OBSBasic::EditSceneName() -{ - ui->scenesDock->removeAction(renameScene); - QListWidgetItem *item = ui->scenes->currentItem(); - Qt::ItemFlags flags = item->flags(); - - item->setFlags(flags | Qt::ItemIsEditable); - ui->scenes->editItem(item); - item->setFlags(flags); -} - -QList OBSBasic::GetProjectorMenuMonitorsFormatted() -{ - QList projectorsFormatted; - QList screens = QGuiApplication::screens(); - for (int i = 0; i < screens.size(); i++) { - QScreen *screen = screens[i]; - QRect screenGeometry = screen->geometry(); - qreal ratio = screen->devicePixelRatio(); - QString name = ""; -#if defined(__APPLE__) || defined(_WIN32) - name = screen->name(); -#else - name = screen->model().simplified(); - - if (name.length() > 1 && name.endsWith("-")) - name.chop(1); -#endif - name = name.simplified(); - - if (name.length() == 0) { - name = QString("%1 %2").arg(QTStr("Display")).arg(QString::number(i + 1)); - } - QString str = QString("%1: %2x%3 @ %4,%5") - .arg(name, QString::number(screenGeometry.width() * ratio), - QString::number(screenGeometry.height() * ratio), - QString::number(screenGeometry.x()), QString::number(screenGeometry.y())); - projectorsFormatted.push_back(str); - } - return projectorsFormatted; -} - -void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) -{ - QListWidgetItem *item = ui->scenes->itemAt(pos); - - QMenu popup(this); - QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); - - popup.addAction(QTStr("Add"), this, &OBSBasic::on_actionAddScene_triggered); - - if (item) { - QAction *copyFilters = new QAction(QTStr("Copy.Filters"), this); - copyFilters->setEnabled(false); - connect(copyFilters, &QAction::triggered, this, &OBSBasic::SceneCopyFilters); - QAction *pasteFilters = new QAction(QTStr("Paste.Filters"), this); - pasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource)); - connect(pasteFilters, &QAction::triggered, this, &OBSBasic::ScenePasteFilters); - - popup.addSeparator(); - popup.addAction(QTStr("Duplicate"), this, &OBSBasic::DuplicateSelectedScene); - popup.addAction(copyFilters); - popup.addAction(pasteFilters); - popup.addSeparator(); - popup.addAction(renameScene); - popup.addAction(ui->actionRemoveScene); - popup.addSeparator(); - - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveUp"), this, &OBSBasic::on_actionSceneUp_triggered); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveDown"), this, - &OBSBasic::on_actionSceneDown_triggered); - order.addSeparator(); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToTop"), this, &OBSBasic::MoveSceneToTop); - order.addAction(QTStr("Basic.MainMenu.Edit.Order.MoveToBottom"), this, &OBSBasic::MoveSceneToBottom); - popup.addMenu(&order); - - popup.addSeparator(); - - delete sceneProjectorMenu; - sceneProjectorMenu = new QMenu(QTStr("SceneProjector")); - AddProjectorMenuMonitors(sceneProjectorMenu, this, &OBSBasic::OpenSceneProjector); - popup.addMenu(sceneProjectorMenu); - - QAction *sceneWindow = popup.addAction(QTStr("SceneWindow"), this, &OBSBasic::OpenSceneWindow); - - popup.addAction(sceneWindow); - popup.addAction(QTStr("Screenshot.Scene"), this, &OBSBasic::ScreenshotScene); - popup.addSeparator(); - popup.addAction(QTStr("Filters"), this, &OBSBasic::OpenSceneFilters); - - popup.addSeparator(); - - delete perSceneTransitionMenu; - perSceneTransitionMenu = CreatePerSceneTransitionMenu(); - popup.addMenu(perSceneTransitionMenu); - - /* ---------------------- */ - - QAction *multiviewAction = popup.addAction(QTStr("ShowInMultiview")); - - OBSSource source = GetCurrentSceneSource(); - OBSDataAutoRelease data = obs_source_get_private_settings(source); - - obs_data_set_default_bool(data, "show_in_multiview", true); - bool show = obs_data_get_bool(data, "show_in_multiview"); - - multiviewAction->setCheckable(true); - multiviewAction->setChecked(show); - - auto showInMultiview = [](OBSData data) { - bool show = obs_data_get_bool(data, "show_in_multiview"); - obs_data_set_bool(data, "show_in_multiview", !show); - OBSProjector::UpdateMultiviewProjectors(); - }; - - connect(multiviewAction, &QAction::triggered, std::bind(showInMultiview, data.Get())); - - copyFilters->setEnabled(obs_source_filter_count(source) > 0); - } - - popup.addSeparator(); - - bool grid = ui->scenes->GetGridMode(); - - QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") : QTStr("Basic.Main.GridMode"), this); - connect(gridAction, &QAction::triggered, this, &OBSBasic::GridActionClicked); - popup.addAction(gridAction); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionSceneListMode_triggered() -{ - ui->scenes->SetGridMode(false); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_actionSceneGridMode_triggered() -{ - ui->scenes->SetGridMode(true); - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", true); -} - -void OBSBasic::GridActionClicked() -{ - bool gridMode = !ui->scenes->GetGridMode(); - ui->scenes->SetGridMode(gridMode); - - if (gridMode) - ui->actionSceneGridMode->setChecked(true); - else - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", gridMode); -} - -void OBSBasic::on_actionAddScene_triggered() -{ - string name; - QString format{QTStr("Basic.Main.DefaultSceneName.Text")}; - - int i = 2; - QString placeHolderText = format.arg(i); - OBSSourceAutoRelease source = nullptr; - while ((source = obs_get_source_by_name(QT_TO_UTF8(placeHolderText)))) { - placeHolderText = format.arg(++i); - } - - bool accepted = NameDialog::AskForName(this, QTStr("Basic.Main.AddSceneDlg.Title"), - QTStr("Basic.Main.AddSceneDlg.Text"), name, placeHolderText); - - if (accepted) { - if (name.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - on_actionAddScene_triggered(); - return; - } - - OBSSourceAutoRelease source = obs_get_source_by_name(name.c_str()); - if (source) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - - on_actionAddScene_triggered(); - return; - } - - auto undo_fn = [](const std::string &data) { - obs_source_t *t = obs_get_source_by_name(data.c_str()); - if (t) { - obs_source_remove(t); - obs_source_release(t); - } - }; - - auto redo_fn = [this](const std::string &data) { - OBSSceneAutoRelease scene = obs_scene_create(data.c_str()); - obs_source_t *source = obs_scene_get_source(scene); - SetCurrentScene(source, true); - }; - undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), undo_fn, redo_fn, name, name); - - OBSSceneAutoRelease scene = obs_scene_create(name.c_str()); - obs_source_t *scene_source = obs_scene_get_source(scene); - SetCurrentScene(scene_source); - } -} - -void OBSBasic::on_actionRemoveScene_triggered() -{ - RemoveSelectedScene(); -} - -void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) -{ - int idx = ui->scenes->currentRow(); - if (idx == -1 || idx == invalidIdx) - return; - - ui->scenes->blockSignals(true); - QListWidgetItem *item = ui->scenes->takeItem(idx); - - if (!relative) - idx = 0; - - ui->scenes->insertItem(idx + offset, item); - ui->scenes->setCurrentRow(idx + offset); - item->setSelected(true); - currentScene = GetOBSRef(item).Get(); - ui->scenes->blockSignals(false); - - OBSProjector::UpdateMultiviewProjectors(); -} - -void OBSBasic::on_actionSceneUp_triggered() -{ - ChangeSceneIndex(true, -1, 0); -} - -void OBSBasic::on_actionSceneDown_triggered() -{ - ChangeSceneIndex(true, 1, ui->scenes->count() - 1); -} - -void OBSBasic::MoveSceneToTop() -{ - ChangeSceneIndex(false, 0, 0); -} - -void OBSBasic::MoveSceneToBottom() -{ - ChangeSceneIndex(false, ui->scenes->count() - 1, ui->scenes->count() - 1); -} - -void OBSBasic::EditSceneItemName() -{ - int idx = GetTopSelectedSourceItem(); - ui->sources->Edit(idx); -} - -void OBSBasic::SetDeinterlacingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_mode(source, mode); -} - -void OBSBasic::SetDeinterlacingOrder() -{ - QAction *action = reinterpret_cast(sender()); - obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - - obs_source_set_deinterlace_field_order(source, order); -} - -QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source) -{ - obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source); - obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceMode == mode); - - ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE); - ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD); - ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO); - ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND); - ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X); - ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR); - ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X); - ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF); - ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X); -#undef ADD_MODE - - menu->addSeparator(); - -#define ADD_ORDER(name, order) \ - action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \ - action->setProperty("order", (int)order); \ - action->setCheckable(true); \ - action->setChecked(deinterlaceOrder == order); - - ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP); - ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM); -#undef ADD_ORDER - - return menu; -} - -void OBSBasic::SetScaleFilter() -{ - QAction *action = reinterpret_cast(sender()); - obs_scale_type mode = (obs_scale_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_scale_filter(sceneItem, mode); -} - -QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(scaleFilter == mode); - - ADD_MODE("Disable", OBS_SCALE_DISABLE); - ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT); - ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR); - ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC); - ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS); - ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMethod() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_method method = (obs_blending_method)action->property("method").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_method(sceneItem, method); -} - -QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item); - QAction *action; - -#define ADD_MODE(name, method) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \ - action->setProperty("method", (int)method); \ - action->setCheckable(true); \ - action->setChecked(blendingMethod == method); - - ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT); - ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF); -#undef ADD_MODE - - return menu; -} - -void OBSBasic::SetBlendingMode() -{ - QAction *action = reinterpret_cast(sender()); - obs_blending_type mode = (obs_blending_type)action->property("mode").toInt(); - OBSSceneItem sceneItem = GetCurrentSceneItem(); - - obs_sceneitem_set_blending_mode(sceneItem, mode); -} - -QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item) -{ - obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item); - QAction *action; - -#define ADD_MODE(name, mode) \ - action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \ - action->setProperty("mode", (int)mode); \ - action->setCheckable(true); \ - action->setChecked(blendingMode == mode); - - ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL); - ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE); - ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT); - ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN); - ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY); - ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN); - ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN); -#undef ADD_MODE - - return menu; -} - -QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select, - obs_sceneitem_t *item) -{ - QAction *action; - - menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}" - "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}" - "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}" - "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}" - "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}" - "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" - "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" - "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}")); - - obs_data_t *privData = obs_sceneitem_get_private_settings(item); - obs_data_release(privData); - - obs_data_set_default_int(privData, "color-preset", 0); - int preset = obs_data_get_int(privData, "color-preset"); - - action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 0); - action->setChecked(preset == 0); - - action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange); - action->setCheckable(true); - action->setProperty("bgColor", 1); - action->setChecked(preset == 1); - - menu->addSeparator(); - - widgetAction->setDefaultWidget(select); - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *colorButton = select->findChild(button.str().c_str()); - if (preset == i + 1) - colorButton->setStyleSheet("border: 2px solid black"); - - colorButton->setProperty("bgColor", i); - select->connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange); - } - - menu->addAction(widgetAction); - - return menu; -} - -ColorSelect::ColorSelect(QWidget *parent) : QWidget(parent), ui(new Ui::ColorSelect) -{ - ui->setupUi(this); -} - -void OBSBasic::CreateSourcePopupMenu(int idx, bool preview) -{ - QMenu popup(this); - delete previewProjectorSource; - delete sourceProjector; - delete scaleFilteringMenu; - delete blendingMethodMenu; - delete blendingModeMenu; - delete colorMenu; - delete colorWidgetAction; - delete colorSelect; - delete deinterlaceMenu; - - if (preview) { - QAction *action = - popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - if (IsPreviewProgramMode()) - action->setEnabled(false); - - popup.addAction(ui->actionLockPreview); - popup.addMenu(ui->scalingMenu); - - previewProjectorSource = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector); - - popup.addMenu(previewProjectorSource); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addAction(previewWindow); - - popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene); - - popup.addSeparator(); - } - - QPointer addSourceMenu = CreateAddSourcePopupMenu(); - if (addSourceMenu) - popup.addMenu(addSourceMenu); - - if (ui->sources->MultipleBaseSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems); - - } else if (ui->sources->GroupsSelected()) { - popup.addSeparator(); - popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups); - } - - popup.addSeparator(); - popup.addAction(ui->actionCopySource); - popup.addAction(ui->actionPasteRef); - popup.addAction(ui->actionPasteDup); - popup.addSeparator(); - - popup.addSeparator(); - popup.addAction(ui->actionCopyFilters); - popup.addAction(ui->actionPasteFilters); - popup.addSeparator(); - - if (idx != -1) { - if (addSourceMenu) - popup.addSeparator(); - - OBSSceneItem sceneItem = ui->sources->Get(idx); - obs_source_t *source = obs_sceneitem_get_source(sceneItem); - uint32_t flags = obs_source_get_output_flags(source); - bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO; - bool hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO; - bool hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO; - - colorMenu = new QMenu(QTStr("ChangeBG")); - colorWidgetAction = new QWidgetAction(colorMenu); - colorSelect = new ColorSelect(colorMenu); - popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem)); - popup.addAction(renameSource); - popup.addAction(ui->actionRemoveSource); - popup.addSeparator(); - - popup.addMenu(ui->orderMenu); - - if (hasVideo) - popup.addMenu(ui->transformMenu); - - popup.addSeparator(); - - if (hasAudio) { - QAction *actionHideMixer = - popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer); - actionHideMixer->setCheckable(true); - actionHideMixer->setChecked(SourceMixerHidden(source)); - popup.addSeparator(); - } - - if (hasVideo) { - QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this, - &OBSBasic::ResizeOutputSizeOfSource); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - resizeOutput->setEnabled(!obs_video_active()); - - if (width < 32 || height < 32) - resizeOutput->setEnabled(false); - - scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering")); - popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem)); - blendingModeMenu = new QMenu(QTStr("BlendingMode")); - popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem)); - blendingMethodMenu = new QMenu(QTStr("BlendingMethod")); - popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem)); - if (isAsyncVideo) { - deinterlaceMenu = new QMenu(QTStr("Deinterlacing")); - popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source)); - } - - popup.addSeparator(); - - popup.addMenu(CreateVisibilityTransitionMenu(true)); - popup.addMenu(CreateVisibilityTransitionMenu(false)); - popup.addSeparator(); - - sourceProjector = new QMenu(QTStr("SourceProjector")); - AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector); - popup.addMenu(sourceProjector); - popup.addAction(QTStr("SourceWindow"), this, &OBSBasic::OpenSourceWindow); - - popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource); - } - - popup.addSeparator(); - - if (flags & OBS_SOURCE_INTERACTION) - popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered); - - popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); }); - QAction *action = - popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered); - action->setEnabled(obs_source_configurable(source)); - } - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos) -{ - if (ui->scenes->count()) { - QModelIndex idx = ui->sources->indexAt(pos); - CreateSourcePopupMenu(idx.row(), false); - } -} - -void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) -{ - if (!witem) - return; - - if (IsPreviewProgramMode()) { - bool doubleClickSwitch = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "TransitionOnDoubleClick"); - - if (doubleClickSwitch) - TransitionClicked(); - } -} - -static inline bool should_show_properties(obs_source_t *source, const char *id) -{ - if (!source) - return false; - if (strcmp(id, "group") == 0) - return false; - if (!obs_source_configurable(source)) - return false; - - uint32_t caps = obs_source_get_output_flags(source); - if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0) - return false; - - return true; -} - -void OBSBasic::AddSource(const char *id) -{ - if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id, undo_s); - sourceSelect.exec(); - if (should_show_properties(sourceSelect.newSource, id)) { - CreatePropertiesWindow(sourceSelect.newSource); - } - } -} - -QMenu *OBSBasic::CreateAddSourcePopupMenu() -{ - const char *unversioned_type; - const char *type; - bool foundValues = false; - bool foundDeprecated = false; - size_t idx = 0; - - QMenu *popup = new QMenu(QTStr("Add"), this); - QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup); - - auto getActionAfter = [](QMenu *menu, const QString &name) { - QList actions = menu->actions(); - - for (QAction *menuAction : actions) { - if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0) - return menuAction; - } - - return (QAction *)nullptr; - }; - - auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) { - QString qname = QT_UTF8(name); - QAction *popupItem = new QAction(qname, this); - connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); }); - - QIcon icon; - - if (strcmp(type, "scene") == 0) - icon = GetSceneIcon(); - else - icon = GetSourceIcon(type); - - popupItem->setIcon(icon); - - QAction *after = getActionAfter(popup, qname); - popup->insertAction(after, popupItem); - }; - - while (obs_enum_input_types2(idx++, &type, &unversioned_type)) { - const char *name = obs_source_get_display_name(type); - uint32_t caps = obs_get_source_output_flags(type); - - if ((caps & OBS_SOURCE_CAP_DISABLED) != 0) - continue; - - if ((caps & OBS_SOURCE_DEPRECATED) == 0) { - addSource(popup, unversioned_type, name); - } else { - addSource(deprecated, unversioned_type, name); - foundDeprecated = true; - } - foundValues = true; - } - - addSource(popup, "scene", Str("Basic.Scene")); - - popup->addSeparator(); - QAction *addGroup = new QAction(QTStr("Group"), this); - addGroup->setIcon(GetGroupIcon()); - connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); }); - popup->addAction(addGroup); - - if (!foundDeprecated) { - delete deprecated; - deprecated = nullptr; - } - - if (!foundValues) { - delete popup; - popup = nullptr; - - } else if (foundDeprecated) { - popup->addSeparator(); - popup->addMenu(deprecated); - } - - return popup; -} - -void OBSBasic::AddSourcePopupMenu(const QPoint &pos) -{ - if (!GetCurrentScene()) { - // Tell the user he needs a scene first (help beginners). - OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"), - QTStr("Basic.Main.AddSourceHelp.Text")); - return; - } - - QScopedPointer popup(CreateAddSourcePopupMenu()); - if (popup) - popup->exec(pos); -} - -void OBSBasic::on_actionAddSource_triggered() -{ - AddSourcePopupMenu(QCursor::pos()); -} - -static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - vector &items = *reinterpret_cast *>(param); - - if (obs_sceneitem_selected(item)) { - items.emplace_back(item); - } else if (obs_sceneitem_is_group(item)) { - obs_sceneitem_group_enum_items(item, remove_items, &items); - } - return true; -}; - -OBSData OBSBasic::BackupScene(obs_scene_t *scene, std::vector *sources) -{ - OBSDataArrayAutoRelease undo_array = obs_data_array_create(); - - if (!sources) { - obs_scene_enum_items(scene, save_undo_source_enum, undo_array); - } else { - for (obs_source_t *source : *sources) { - obs_data_t *source_data = obs_save_source(source); - obs_data_array_push_back(undo_array, source_data); - obs_data_release(source_data); - } - } - - OBSDataAutoRelease scene_data = obs_save_source(obs_scene_get_source(scene)); - obs_data_array_push_back(undo_array, scene_data); - - OBSDataAutoRelease data = obs_data_create(); - - obs_data_set_array(data, "array", undo_array); - obs_data_get_json(data); - return data.Get(); -} - -static bool add_source_enum(obs_scene_t *, obs_sceneitem_t *item, void *p) -{ - auto sources = static_cast *>(p); - sources->push_back(obs_sceneitem_get_source(item)); - return true; -} - -void OBSBasic::CreateSceneUndoRedoAction(const QString &action_name, OBSData undo_data, OBSData redo_data) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease base = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(base, "array"); - std::vector sources; - std::vector old_sources; - - /* create missing sources */ - const size_t count = obs_data_array_count(array); - sources.reserve(count); - - for (size_t i = 0; i < count; i++) { - OBSDataAutoRelease data = obs_data_array_item(array, i); - const char *name = obs_data_get_string(data, "name"); - - OBSSourceAutoRelease source = obs_get_source_by_name(name); - if (!source) - source = obs_load_source(data); - - sources.push_back(source.Get()); - - /* update scene/group settings to restore their - * contents to their saved settings */ - obs_scene_t *scene = obs_group_or_scene_from_source(source); - if (scene) { - obs_scene_enum_items(scene, add_source_enum, &old_sources); - OBSDataAutoRelease scene_settings = obs_data_get_obj(data, "settings"); - obs_source_update(source, scene_settings); - } - } - - /* actually load sources now */ - for (obs_source_t *source : sources) - obs_source_load2(source); - - ui->sources->RefreshItems(); - }; - - const char *undo_json = obs_data_get_last_json(undo_data); - const char *redo_json = obs_data_get_last_json(redo_data); - - undo_s.add_action(action_name, undo_redo, undo_redo, undo_json, redo_json); -} - -void OBSBasic::on_actionRemoveSource_triggered() -{ - vector items; - OBSScene scene = GetCurrentScene(); - obs_source_t *scene_source = obs_scene_get_source(scene); - - obs_scene_enum_items(scene, remove_items, &items); - - if (!items.size()) - return; - - /* ------------------------------------- */ - /* confirm action with user */ - - bool confirmed = false; - - if (items.size() > 1) { - QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size())); - - QMessageBox remove_items(this); - remove_items.setText(text); - QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole); - remove_items.setDefaultButton(Yes); - remove_items.addButton(QTStr("No"), QMessageBox::NoRole); - remove_items.setIcon(QMessageBox::Question); - remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); - remove_items.exec(); - - confirmed = Yes == remove_items.clickedButton(); - } else { - OBSSceneItem &item = items[0]; - obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - confirmed = true; - } - if (!confirmed) - return; - - /* ----------------------------------------------- */ - /* save undo data */ - - OBSData undo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* remove items */ - - for (auto &item : items) - obs_sceneitem_remove(item); - - /* ----------------------------------------------- */ - /* save redo data */ - - OBSData redo_data = BackupScene(scene_source); - - /* ----------------------------------------------- */ - /* add undo/redo action */ - - QString action_name; - if (items.size() > 1) { - action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size())); - } else { - QString str = QTStr("Undo.Delete"); - action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0]))); - } - - CreateSceneUndoRedoAction(action_name, undo_data, redo_data); -} - -void OBSBasic::on_actionInteract_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreateInteractionWindow(source); -} - -void OBSBasic::on_actionSourceProperties_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - OBSSource source = obs_sceneitem_get_source(item); - - if (source) - CreatePropertiesWindow(source); -} - -void OBSBasic::MoveSceneItem(enum obs_order_movement movement, const QString &action_name) -{ - OBSSceneItem item = GetCurrentSceneItem(); - obs_source_t *source = obs_sceneitem_get_source(item); - - if (!source) - return; - - OBSScene scene = GetCurrentScene(); - std::vector sources; - if (scene != obs_sceneitem_get_scene(item)) - sources.push_back(obs_scene_get_source(obs_sceneitem_get_scene(item))); - - OBSData undo_data = BackupScene(scene, &sources); - - obs_sceneitem_set_order(item, movement); - - const char *source_name = obs_source_get_name(source); - const char *scene_name = obs_source_get_name(obs_scene_get_source(scene)); - - OBSData redo_data = BackupScene(scene, &sources); - CreateSceneUndoRedoAction(action_name.arg(source_name, scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionSourceUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionSourceDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveUp_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp")); -} - -void OBSBasic::on_actionMoveDown_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown")); -} - -void OBSBasic::on_actionMoveToTop_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop")); -} - -void OBSBasic::on_actionMoveToBottom_triggered() -{ - MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom")); -} - -static BPtr ReadLogFile(const char *subdir, const char *log) -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), subdir) <= 0) - return nullptr; - - string path = logDir; - path += "/"; - path += log; - - BPtr file = os_quick_read_utf8_file(path.c_str()); - if (!file) - blog(LOG_WARNING, "Failed to read log file %s", path.c_str()); - - return file; -} - -void OBSBasic::UploadLog(const char *subdir, const char *file, const bool crash) -{ - BPtr fileString{ReadLogFile(subdir, file)}; - - if (!fileString) - return; - - if (!*fileString) - return; - - ui->menuLogFiles->setEnabled(false); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(false); -#endif - - stringstream ss; - ss << "OBS " << App()->GetVersionString(false) << " log file uploaded at " << CurrentDateTimeString() << "\n\n" - << fileString; - - if (logUploadThread) { - logUploadThread->wait(); - } - - RemoteTextThread *thread = new RemoteTextThread("https://obsproject.com/logs/upload", "text/plain", ss.str()); - - logUploadThread.reset(thread); - if (crash) { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::crashUploadFinished); - } else { - connect(thread, &RemoteTextThread::Result, this, &OBSBasic::logUploadFinished); - } - logUploadThread->start(); -} - -void OBSBasic::on_actionShowLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/logs") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadCurrentLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetCurrentLog(), false); -} - -void OBSBasic::on_actionUploadLastLog_triggered() -{ - UploadLog("obs-studio/logs", App()->GetLastLog(), false); -} - -void OBSBasic::on_actionViewCurrentLog_triggered() -{ - if (!logView) - logView = new OBSLogViewer(); - - logView->show(); - logView->setWindowState((logView->windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); - logView->activateWindow(); - logView->raise(); -} - -void OBSBasic::on_actionShowCrashLogs_triggered() -{ - char logDir[512]; - if (GetAppConfigPath(logDir, sizeof(logDir), "obs-studio/crashes") <= 0) - return; - - QUrl url = QUrl::fromLocalFile(QT_UTF8(logDir)); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionUploadLastCrashLog_triggered() -{ - UploadLog("obs-studio/crashes", App()->GetLastCrashLog(), true); -} - -void OBSBasic::on_actionCheckForUpdates_triggered() -{ - CheckForUpdates(true); -} - -void OBSBasic::on_actionRepair_triggered() -{ -#if defined(_WIN32) - ui->actionCheckForUpdates->setEnabled(false); - ui->actionRepair->setEnabled(false); - - if (updateCheckThread && updateCheckThread->isRunning()) - return; - - updateCheckThread.reset(new AutoUpdateThread(false, true)); - updateCheckThread->start(); -#endif -} - -void OBSBasic::on_actionRestartSafe_triggered() -{ - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("Restart"), safe_mode ? QTStr("SafeMode.RestartNormal") : QTStr("SafeMode.Restart")); - - if (button == QMessageBox::Yes) { - restart = safe_mode; - restart_safe = !safe_mode; - close(); - } -} - -void OBSBasic::logUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, false); -} - -void OBSBasic::crashUploadFinished(const QString &text, const QString &error) -{ - ui->menuLogFiles->setEnabled(true); -#if defined(_WIN32) - ui->menuCrashLogs->setEnabled(true); -#endif - - if (text.isEmpty()) { - OBSMessageBox::critical(this, QTStr("LogReturnDialog.ErrorUploadingLog"), error); - return; - } - openLogDialog(text, true); -} - -void OBSBasic::openLogDialog(const QString &text, const bool crash) -{ - - OBSDataAutoRelease returnData = obs_data_create_from_json(QT_TO_UTF8(text)); - string resURL = obs_data_get_string(returnData, "url"); - QString logURL = resURL.c_str(); - - OBSLogReply logDialog(this, logURL, crash); - logDialog.exec(); -} - -static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_t *source, const string &name) -{ - const char *prevName = obs_source_get_name(source); - if (name == prevName) - return; - - OBSSourceAutoRelease foundSource = obs_get_source_by_name(name.c_str()); - QListWidgetItem *listItem = listWidget->currentItem(); - - if (foundSource || name.empty()) { - listItem->setText(QT_UTF8(prevName)); - - if (foundSource) { - OBSMessageBox::warning(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - } else if (name.empty()) { - OBSMessageBox::warning(parent, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - } - } else { - auto undo = [prev = std::string(prevName)](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, prev.c_str()); - }; - - auto redo = [name](const std::string &data) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(data.c_str()); - obs_source_set_name(source, name.c_str()); - }; - - std::string source_uuid(obs_source_get_uuid(source)); - parent->undo_s.add_action(QTStr("Undo.Rename").arg(name.c_str()), undo, redo, source_uuid, source_uuid); - - listItem->setText(QT_UTF8(name.c_str())); - obs_source_set_name(source, name.c_str()); - } -} - -void OBSBasic::SceneNameEdited(QWidget *editor) -{ - OBSScene scene = GetCurrentScene(); - QLineEdit *edit = qobject_cast(editor); - string text = QT_TO_UTF8(edit->text().trimmed()); - - if (!scene) - return; - - obs_source_t *source = obs_scene_get_source(scene); - RenameListItem(this, ui->scenes, source, text); - - ui->scenesDock->addAction(renameScene); - - OnEvent(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); -} - -void OBSBasic::OpenFilters(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateFiltersWindow(source); -} - -void OBSBasic::OpenProperties(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreatePropertiesWindow(source); -} - -void OBSBasic::OpenInteraction(OBSSource source) -{ - if (source == nullptr) { - OBSSceneItem item = GetCurrentSceneItem(); - source = obs_sceneitem_get_source(item); - } - CreateInteractionWindow(source); -} - -void OBSBasic::OpenEditTransform(OBSSceneItem item) -{ - if (!item) - item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::OpenSceneFilters() -{ - OBSScene scene = GetCurrentScene(); - OBSSource source = obs_scene_get_source(scene); - - CreateFiltersWindow(source); -} - -#define RECORDING_START "==== Recording Start ===============================================" -#define RECORDING_STOP "==== Recording Stop ================================================" -#define REPLAY_BUFFER_START "==== Replay Buffer Start ===========================================" -#define REPLAY_BUFFER_STOP "==== Replay Buffer Stop ============================================" -#define STREAMING_START "==== Streaming Start ===============================================" -#define STREAMING_STOP "==== Streaming Stop ================================================" -#define VIRTUAL_CAM_START "==== Virtual Camera Start ==========================================" -#define VIRTUAL_CAM_STOP "==== Virtual Camera Stop ===========================================" - -void OBSBasic::DisplayStreamStartError() -{ - QString message = !outputHandler->lastError.empty() ? QTStr(outputHandler->lastError.c_str()) - : QTStr("Output.StartFailedGeneric"); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - QMessageBox::critical(this, QTStr("Output.StartStreamFailed"), message); -} +extern bool cef_js_avail; #ifdef YOUTUBE_ENABLED void OBSBasic::YouTubeActionDialogOk(const QString &broadcast_id, const QString &stream_id, const QString &key, @@ -6480,82 +129,6 @@ void OBSBasic::ShowYouTubeAutoStartWarning() } #endif -void OBSBasic::StartStreaming() -{ - if (outputHandler->StreamingActive()) - return; - if (disableOutputsRef) - return; - - if (auth && auth->broadcastFlow()) { - if (!broadcastActive && !broadcastReady) { - QMessageBox no_broadcast(this); - no_broadcast.setText(QTStr("Output.NoBroadcast.Text")); - QPushButton *SetupBroadcast = - no_broadcast.addButton(QTStr("Basic.Main.SetupBroadcast"), QMessageBox::YesRole); - no_broadcast.setDefaultButton(SetupBroadcast); - no_broadcast.addButton(QTStr("Close"), QMessageBox::NoRole); - no_broadcast.setIcon(QMessageBox::Information); - no_broadcast.setWindowTitle(QTStr("Output.NoBroadcast.Title")); - no_broadcast.exec(); - - if (no_broadcast.clickedButton() == SetupBroadcast) - QMetaObject::invokeMethod(this, "SetupBroadcast"); - return; - } - } - - emit StreamingPreparing(); - - if (sysTrayStream) { - sysTrayStream->setEnabled(false); - sysTrayStream->setText("Basic.Main.PreparingStream"); - } - - auto finish_stream_setup = [&](bool setupStreamingResult) { - if (!setupStreamingResult) { - DisplayStreamStartError(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTING); - - SaveProject(); - - emit StreamingStarting(autoStartBroadcast); - - if (sysTrayStream) - sysTrayStream->setText("Basic.Main.Connecting"); - - if (!outputHandler->StartStreaming(service)) { - DisplayStreamStartError(); - return; - } - - if (autoStartBroadcast) { - emit BroadcastStreamStarted(autoStopBroadcast); - broadcastActive = true; - } - - bool recordWhenStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - if (recordWhenStreaming) - StartRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - if (replayBufferWhileStreaming) - StartReplayBuffer(); - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) - OBSBasic::ShowYouTubeAutoStartWarning(); -#endif - }; - - setupStreamingGuard = outputHandler->SetupStreaming(service, finish_stream_setup); -} - void OBSBasic::BroadcastButtonClicked() { if (!broadcastReady || (!broadcastActive && !outputHandler->StreamingActive())) { @@ -6637,1157 +210,6 @@ void OBSBasic::SetupBroadcast() #endif } -#ifdef _WIN32 -static inline void UpdateProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority(priority); -} - -static inline void ClearProcessPriority() -{ - const char *priority = config_get_string(App()->GetAppConfig(), "General", "ProcessPriority"); - if (priority && strcmp(priority, "Normal") != 0) - SetProcessPriority("Normal"); -} -#else -#define UpdateProcessPriority() \ - do { \ - } while (false) -#define ClearProcessPriority() \ - do { \ - } while (false) -#endif - -inline void OBSBasic::OnActivate(bool force) -{ - if (ui->profileMenu->isEnabled() || force) { - ui->profileMenu->setEnabled(false); - ui->autoConfigure->setEnabled(false); - App()->IncrementSleepInhibition(); - UpdateProcessPriority(); - - struct obs_video_info ovi; - obs_get_video_info(&ovi); - lastOutputResolution = {ovi.base_width, ovi.base_height}; - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayMask = QIcon(":/res/images/tray_active_macos.svg"); - trayMask.setIsMask(true); - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayMask)); -#else - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", QIcon(":/res/images/tray_active.png"))); -#endif - } - } -} - -extern volatile bool recording_paused; -extern volatile bool replaybuf_active; - -inline void OBSBasic::OnDeactivate() -{ - if (!outputHandler->Active() && !ui->profileMenu->isEnabled()) { - ui->profileMenu->setEnabled(true); - ui->autoConfigure->setEnabled(true); - App()->DecrementSleepInhibition(); - ClearProcessPriority(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusInactive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray", trayIconFile)); - } - } else if (outputHandler->Active() && trayIcon && trayIcon->isVisible()) { - if (os_atomic_load_bool(&recording_paused)) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - } else { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - } - } -} - -void OBSBasic::StopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(streamingStopping); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::ForceStopStreaming() -{ - SaveProject(); - - if (outputHandler->StreamingActive()) - outputHandler->StopStreaming(true); - - // special case: force reset broadcast state if - // no autostart and no autostop selected - if (!autoStartBroadcast && !broadcastActive) { - broadcastActive = false; - autoStartBroadcast = true; - autoStopBroadcast = true; - broadcastReady = false; - } - - if (autoStopBroadcast) { - broadcastActive = false; - broadcastReady = false; - } - - emit BroadcastStreamReady(broadcastReady); - - OnDeactivate(); - - bool recordWhenStreaming = config_get_bool(App()->GetUserConfig(), "BasicWindow", "RecordWhenStreaming"); - bool keepRecordingWhenStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepRecordingWhenStreamStops"); - if (recordWhenStreaming && !keepRecordingWhenStreamStops) - StopRecording(); - - bool replayBufferWhileStreaming = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "ReplayBufferWhileStreaming"); - bool keepReplayBufferStreamStops = - config_get_bool(App()->GetUserConfig(), "BasicWindow", "KeepReplayBufferStreamStops"); - if (replayBufferWhileStreaming && !keepReplayBufferStreamStops) - StopReplayBuffer(); -} - -void OBSBasic::StreamDelayStarting(int sec) -{ - emit StreamingStarted(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStarting(sec); - - OnActivate(); -} - -void OBSBasic::StreamDelayStopping(int sec) -{ - emit StreamingStopped(true); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - ui->statusbar->StreamDelayStopping(sec); - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStart() -{ - emit StreamingStarted(); - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - ui->statusbar->StreamStarted(output); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StopStreaming")); - sysTrayStream->setEnabled(true); - } - -#ifdef YOUTUBE_ENABLED - if (!autoStartBroadcast) { - // get a current stream key - obs_service_t *service_obj = GetService(); - OBSDataAutoRelease settings = obs_service_get_settings(service_obj); - std::string key = obs_data_get_string(settings, "stream_id"); - if (!key.empty() && !youtubeStreamCheckThread) { - youtubeStreamCheckThread = CreateQThread([this, key] { YoutubeStreamCheck(key); }); - youtubeStreamCheckThread->setObjectName("YouTubeStreamCheckThread"); - youtubeStreamCheckThread->start(); - } - } -#endif - - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STARTED); - - OnActivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStarted(); -#endif - - blog(LOG_INFO, STREAMING_START); -} - -void OBSBasic::StreamStopping() -{ - emit StreamingStopping(); - - if (sysTrayStream) - sysTrayStream->setText(QTStr("Basic.Main.StoppingStreaming")); - - streamingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPING); -} - -void OBSBasic::StreamingStop(int code, QString last_error) -{ - const char *errorDescription = ""; - DStr errorMessage; - bool use_last_error = false; - bool encode_error = false; - - switch (code) { - case OBS_OUTPUT_BAD_PATH: - errorDescription = Str("Output.ConnectFail.BadPath"); - break; - - case OBS_OUTPUT_CONNECT_FAILED: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.ConnectFailed"); - break; - - case OBS_OUTPUT_INVALID_STREAM: - errorDescription = Str("Output.ConnectFail.InvalidStream"); - break; - - case OBS_OUTPUT_ENCODE_ERROR: - encode_error = true; - break; - - case OBS_OUTPUT_HDR_DISABLED: - errorDescription = Str("Output.ConnectFail.HdrDisabled"); - break; - - default: - case OBS_OUTPUT_ERROR: - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Error"); - break; - - case OBS_OUTPUT_DISCONNECTED: - /* doesn't happen if output is set to reconnect. note that - * reconnects are handled in the output, not in the UI */ - use_last_error = true; - errorDescription = Str("Output.ConnectFail.Disconnected"); - } - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s\n\n%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - ui->statusbar->StreamStopped(); - - emit StreamingStopped(); - - if (sysTrayStream) { - sysTrayStream->setText(QTStr("Basic.Main.StartStreaming")); - sysTrayStream->setEnabled(true); - } - - streamingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_STREAMING_STOPPED); - - OnDeactivate(); - -#ifdef YOUTUBE_ENABLED - if (YouTubeAppDock::IsYTServiceSelected()) - youtubeAppDock->IngestionStopped(); -#endif - - blog(LOG_INFO, STREAMING_STOP); - - if (encode_error) { - QString msg = last_error.isEmpty() ? QTStr("Output.StreamEncodeError.Msg") - : QTStr("Output.StreamEncodeError.Msg.LastError").arg(last_error); - OBSMessageBox::information(this, QTStr("Output.StreamEncodeError.Title"), msg); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::information(this, QTStr("Output.ConnectFail.Title"), QT_UTF8(errorMessage)); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QT_UTF8(errorDescription), QSystemTrayIcon::Warning); - } - - // Reset broadcast button state/text - if (!broadcastActive) - SetBroadcastFlowEnabled(auth && auth->broadcastFlow()); -} - -void OBSBasic::AutoRemux(QString input, bool no_show) -{ - auto config = Config(); - - bool autoRemux = config_get_bool(config, "Video", "AutoRemux"); - - if (!autoRemux) - return; - - bool isSimpleMode = false; - - const char *mode = config_get_string(config, "Output", "Mode"); - if (!mode) { - isSimpleMode = true; - } else { - isSimpleMode = strcmp(mode, "Simple") == 0; - } - - if (!isSimpleMode) { - const char *recType = config_get_string(config, "AdvOut", "RecType"); - - bool ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0; - - if (ffmpegOutput) - return; - } - - if (input.isEmpty()) - return; - - QFileInfo fi(input); - QString suffix = fi.suffix(); - - /* do not remux if lossless */ - if (suffix.compare("avi", Qt::CaseInsensitive) == 0) { - return; - } - - QString path = fi.path(); - - QString output = input; - output.resize(output.size() - suffix.size()); - - const obs_encoder_t *videoEncoder = obs_output_get_video_encoder(outputHandler->fileOutput); - const char *vCodecName = obs_encoder_get_codec(videoEncoder); - const char *format = config_get_string(config, isSimpleMode ? "SimpleOutput" : "AdvOut", "RecFormat2"); - - /* Retain original container for fMP4/fMOV */ - if (strncmp(format, "fragmented", 10) == 0) { - output += "remuxed." + suffix; - } else if (strcmp(vCodecName, "prores") == 0) { - output += "mov"; - } else { - output += "mp4"; - } - - OBSRemux *remux = new OBSRemux(QT_TO_UTF8(path), this, true); - if (!no_show) - remux->show(); - remux->AutoRemux(input, output); -} - -void OBSBasic::StartRecording() -{ - if (outputHandler->RecordingActive()) - return; - if (disableOutputsRef) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (!IsFFmpegOutputToURL() && LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTING); - - SaveProject(); - - outputHandler->StartRecording(); -} - -void OBSBasic::RecordStopping() -{ - emit RecordingStopping(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StoppingRecording")); - - recordingStopping = true; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPING); -} - -void OBSBasic::StopRecording() -{ - SaveProject(); - - if (outputHandler->RecordingActive()) - outputHandler->StopRecording(recordingStopping); - - OnDeactivate(); -} - -void OBSBasic::RecordingStart() -{ - ui->statusbar->RecordingStarted(outputHandler->fileOutput); - emit RecordingStarted(isRecordingPausable); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StopRecording")); - - recordingStopping = false; - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STARTED); - - if (!diskFullTimer->isActive()) - diskFullTimer->start(1000); - - OnActivate(); - - blog(LOG_INFO, RECORDING_START); -} - -void OBSBasic::RecordingStop(int code, QString last_error) -{ - ui->statusbar->RecordingStopped(); - emit RecordingStopped(); - - if (sysTrayRecord) - sysTrayRecord->setText(QTStr("Basic.Main.StartRecording")); - - blog(LOG_INFO, RECORDING_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_ENCODE_ERROR && isVisible()) { - QString msg = last_error.isEmpty() - ? QTStr("Output.RecordError.EncodeErrorMsg") - : QTStr("Output.RecordError.EncodeErrorMsg.LastError").arg(last_error); - OBSMessageBox::warning(this, QTStr("Output.RecordError.Title"), msg); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - - const char *errorDescription; - DStr errorMessage; - bool use_last_error = true; - - errorDescription = Str("Output.RecordError.Msg"); - - if (use_last_error && !last_error.isEmpty()) - dstr_printf(errorMessage, "%s

%s", errorDescription, QT_TO_UTF8(last_error)); - else - dstr_copy(errorMessage, errorDescription); - - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QT_UTF8(errorMessage)); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } else if (code == OBS_OUTPUT_SUCCESS) { - if (outputHandler) { - std::string path = outputHandler->lastRecordingPath; - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(QT_UTF8(path.c_str()))); - } - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_STOPPED); - - if (diskFullTimer->isActive()) - diskFullTimer->stop(); - - AutoRemux(outputHandler->lastRecordingPath.c_str()); - - OnDeactivate(); -} - -void OBSBasic::RecordingFileChanged(QString lastRecordingPath) -{ - QString str = QTStr("Basic.StatusBar.RecordingSavedTo"); - ShowStatusBarMessage(str.arg(lastRecordingPath)); - - AutoRemux(lastRecordingPath, true); -} - -void OBSBasic::ShowReplayBufferPauseWarning() -{ - auto msgBox = []() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("Output.ReplayBuffer." - "PauseWarning.Title")); - msgbox.setText(QTStr("Output.ReplayBuffer." - "PauseWarning.Text")); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - bool warned = config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutReplayBufferPausing"); - if (!warned) { - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); - } -} - -void OBSBasic::StartReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (outputHandler->ReplayBufferActive()) - return; - if (disableOutputsRef) - return; - - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - if (!OutputPathValid()) { - OutputPathInvalidMessage(); - return; - } - - if (LowDiskSpace()) { - DiskSpaceMessage(); - return; - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING); - - SaveProject(); - - if (outputHandler->StartReplayBuffer() && os_atomic_load_bool(&recording_paused)) { - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::ReplayBufferStopping() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopping(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StoppingReplayBuffer")); - - replayBufferStopping = true; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING); -} - -void OBSBasic::StopReplayBuffer() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - SaveProject(); - - if (outputHandler->ReplayBufferActive()) - outputHandler->StopReplayBuffer(replayBufferStopping); - - OnDeactivate(); -} - -void OBSBasic::ReplayBufferStart() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStarted(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StopReplayBuffer")); - - replayBufferStopping = false; - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED); - - OnActivate(); - - blog(LOG_INFO, REPLAY_BUFFER_START); -} - -void OBSBasic::ReplayBufferSave() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "save", &cd); - calldata_free(&cd); -} - -void OBSBasic::ReplayBufferSaved() -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - if (!outputHandler->ReplayBufferActive()) - return; - - calldata_t cd = {0}; - proc_handler_t *ph = obs_output_get_proc_handler(outputHandler->replayBuffer); - proc_handler_call(ph, "get_last_replay", &cd); - std::string path = calldata_string(&cd, "path"); - QString msg = QTStr("Basic.StatusBar.ReplayBufferSavedTo").arg(QT_UTF8(path.c_str())); - ShowStatusBarMessage(msg); - lastReplay = path; - calldata_free(&cd); - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED); - - AutoRemux(QT_UTF8(path.c_str())); -} - -void OBSBasic::ReplayBufferStop(int code) -{ - if (!outputHandler || !outputHandler->replayBuffer) - return; - - emit ReplayBufStopped(); - - if (sysTrayReplayBuffer) - sysTrayReplayBuffer->setText(QTStr("Basic.Main.StartReplayBuffer")); - - blog(LOG_INFO, REPLAY_BUFFER_STOP); - - if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordFail.Title"), QTStr("Output.RecordFail.Unsupported")); - - } else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) { - OBSMessageBox::warning(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); - - } else if (code != OBS_OUTPUT_SUCCESS && isVisible()) { - OBSMessageBox::critical(this, QTStr("Output.RecordError.Title"), QTStr("Output.RecordError.Msg")); - - } else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordFail.Unsupported"), QSystemTrayIcon::Warning); - - } else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"), QSystemTrayIcon::Warning); - - } else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) { - SysTrayNotify(QTStr("Output.RecordError.Msg"), QSystemTrayIcon::Warning); - } - - OnEvent(OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED); - - OnDeactivate(); -} - -void OBSBasic::StartVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - if (outputHandler->VirtualCamActive()) - return; - if (disableOutputsRef) - return; - - SaveProject(); - - outputHandler->StartVirtualCam(); -} - -void OBSBasic::StopVirtualCam() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - SaveProject(); - - if (outputHandler->VirtualCamActive()) - outputHandler->StopVirtualCam(); - - OnDeactivate(); -} - -void OBSBasic::OnVirtualCamStart() -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStarted(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED); - - OnActivate(); - - blog(LOG_INFO, VIRTUAL_CAM_START); -} - -void OBSBasic::OnVirtualCamStop(int) -{ - if (!outputHandler || !outputHandler->virtualCam) - return; - - emit VirtualCamStopped(); - - if (sysTrayVirtualCam) - sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam")); - - OnEvent(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED); - - blog(LOG_INFO, VIRTUAL_CAM_STOP); - - OnDeactivate(); - - if (!restartingVCam) - return; - - /* Restarting needs to be delayed to make sure that the virtual camera - * implementation is stopped and avoid race condition. */ - QTimer::singleShot(100, this, &OBSBasic::RestartingVirtualCam); -} - -void OBSBasic::StreamActionTriggered() -{ - if (outputHandler->StreamingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingStream"); - -#ifdef YOUTUBE_ENABLED - if (isVisible() && auth && IsYouTubeService(auth->service()) && autoStopBroadcast) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStop.Title"), QTStr("YouTube.Actions.AutoStopStreamingWarning"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - - confirm = false; - } -#endif - if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStop.Title"), QTStr("ConfirmStop.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StopStreaming(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - Auth *auth = GetAuth(); - - auto action = (auth && auth->external()) ? StreamSettingsAction::ContinueStream - : UIValidation::StreamSettingsConfirmation(this, service); - switch (action) { - case StreamSettingsAction::ContinueStream: - break; - case StreamSettingsAction::OpenSettings: - on_action_Settings_triggered(); - return; - case StreamSettingsAction::Cancel: - return; - } - - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStartingStream"); - - bool bwtest = false; - - if (this->auth) { - OBSDataAutoRelease settings = obs_service_get_settings(service); - bwtest = obs_data_get_bool(settings, "bwtest"); - // Disable confirmation if this is going to open broadcast setup - if (auth && auth->broadcastFlow() && !broadcastReady && !broadcastActive) - confirm = false; - } - - if (bwtest && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question(this, QTStr("ConfirmBWTest.Title"), - QTStr("ConfirmBWTest.Text")); - - if (button == QMessageBox::No) - return; - } else if (confirm && isVisible()) { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ConfirmStart.Title"), QTStr("ConfirmStart.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - - StartStreaming(); - } -} - -void OBSBasic::RecordActionTriggered() -{ - if (outputHandler->RecordingActive()) { - bool confirm = config_get_bool(App()->GetUserConfig(), "BasicWindow", "WarnBeforeStoppingRecord"); - - if (confirm && isVisible()) { - QMessageBox::StandardButton button = OBSMessageBox::question( - this, QTStr("ConfirmStopRecord.Title"), QTStr("ConfirmStopRecord.Text"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No); - - if (button == QMessageBox::No) - return; - } - StopRecording(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartRecording(); - } -} - -void OBSBasic::VirtualCamActionTriggered() -{ - if (outputHandler->VirtualCamActive()) { - StopVirtualCam(); - } else { - if (!UIValidation::NoSourcesConfirmation(this)) - return; - - StartVirtualCam(); - } -} - -void OBSBasic::OpenVirtualCamConfig() -{ - OBSBasicVCamConfig dialog(vcamConfig, outputHandler->VirtualCamActive(), this); - - connect(&dialog, &OBSBasicVCamConfig::Accepted, this, &OBSBasic::UpdateVirtualCamConfig); - connect(&dialog, &OBSBasicVCamConfig::AcceptedAndRestart, this, &OBSBasic::RestartVirtualCam); - - dialog.exec(); -} - -void log_vcam_changed(const VCamConfig &config, bool starting) -{ - const char *action = starting ? "Starting" : "Changing"; - - switch (config.type) { - case VCamOutputType::Invalid: - break; - case VCamOutputType::ProgramView: - blog(LOG_INFO, "%s Virtual Camera output to Program", action); - break; - case VCamOutputType::PreviewOutput: - blog(LOG_INFO, "%s Virtual Camera output to Preview", action); - break; - case VCamOutputType::SceneOutput: - blog(LOG_INFO, "%s Virtual Camera output to Scene : %s", action, config.scene.c_str()); - break; - case VCamOutputType::SourceOutput: - blog(LOG_INFO, "%s Virtual Camera output to Source : %s", action, config.source.c_str()); - break; - } -} - -void OBSBasic::UpdateVirtualCamConfig(const VCamConfig &config) -{ - vcamConfig = config; - - outputHandler->UpdateVirtualCamOutputSource(); - log_vcam_changed(config, false); -} - -void OBSBasic::RestartVirtualCam(const VCamConfig &config) -{ - restartingVCam = true; - - StopVirtualCam(); - - vcamConfig = config; -} - -void OBSBasic::RestartingVirtualCam() -{ - if (!restartingVCam) - return; - - outputHandler->UpdateVirtualCamOutputSource(); - StartVirtualCam(); - restartingVCam = false; -} - -void OBSBasic::on_actionHelpPortal_triggered() -{ - QUrl url = QUrl("https://obsproject.com/help", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionWebsite_triggered() -{ - QUrl url = QUrl("https://obsproject.com", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionDiscord_triggered() -{ - QUrl url = QUrl("https://obsproject.com/discord", QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowWhatsNew_triggered() -{ -#ifdef WHATSNEW_ENABLED - if (introCheckThread && introCheckThread->isRunning()) - return; - if (!cef) - return; - - config_set_int(App()->GetAppConfig(), "General", "InfoIncrement", -1); - - WhatsNewInfoThread *wnit = new WhatsNewInfoThread(); - connect(wnit, &WhatsNewInfoThread::Result, this, &OBSBasic::ReceivedIntroJson, Qt::QueuedConnection); - - introCheckThread.reset(wnit); - introCheckThread->start(); -#endif -} - -void OBSBasic::on_actionReleaseNotes_triggered() -{ - QString addr("https://github.com/obsproject/obs-studio/releases"); - QUrl url(QString("%1/%2").arg(addr, obs_get_version_string()), QUrl::TolerantMode); - QDesktopServices::openUrl(url); -} - -void OBSBasic::on_actionShowSettingsFolder_triggered() -{ - const std::string userConfigPath = App()->userConfigLocation.u8string() + "/obs-studio"; - const QString userConfigLocation = QString::fromStdString(userConfigPath); - - QDesktopServices::openUrl(QUrl::fromLocalFile(userConfigLocation)); -} - -void OBSBasic::on_actionShowProfileFolder_triggered() -{ - try { - const OBSProfile ¤tProfile = GetCurrentProfile(); - QString currentProfileLocation = QString::fromStdString(currentProfile.path.u8string()); - - QDesktopServices::openUrl(QUrl::fromLocalFile(currentProfileLocation)); - } catch (const std::invalid_argument &error) { - blog(LOG_ERROR, "%s", error.what()); - } -} - -int OBSBasic::GetTopSelectedSourceItem() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - return selectedItems.count() ? selectedItems[0].row() : -1; -} - -QModelIndexList OBSBasic::GetAllSelectedSourceItems() -{ - return ui->sources->selectionModel()->selectedIndexes(); -} - -void OBSBasic::on_preview_customContextMenuRequested() -{ - CreateSourcePopupMenu(GetTopSelectedSourceItem(), true); -} - -void OBSBasic::ProgramViewContextMenuRequested() -{ - QMenu popup(this); - QPointer studioProgramProjector; - - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - - popup.addMenu(studioProgramProjector); - popup.addAction(QTStr("StudioProgramWindow"), this, &OBSBasic::OpenStudioProgramWindow); - popup.addAction(QTStr("Screenshot.StudioProgram"), this, &OBSBasic::ScreenshotProgram); - - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_previewDisabledWidget_customContextMenuRequested() -{ - QMenu popup(this); - delete previewProjectorMain; - - QAction *action = popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview); - action->setCheckable(true); - action->setChecked(obs_display_enabled(ui->preview->GetDisplay())); - - previewProjectorMain = new QMenu(QTStr("PreviewProjector")); - AddProjectorMenuMonitors(previewProjectorMain, this, &OBSBasic::OpenPreviewProjector); - - QAction *previewWindow = popup.addAction(QTStr("PreviewWindow"), this, &OBSBasic::OpenPreviewWindow); - - popup.addMenu(previewProjectorMain); - popup.addAction(previewWindow); - popup.exec(QCursor::pos()); -} - -void OBSBasic::on_actionAlwaysOnTop_triggered() -{ -#ifndef _WIN32 - /* Make sure all dialogs are safely and successfully closed before - * switching the always on top mode due to the fact that windows all - * have to be recreated, so queue the actual toggle to happen after - * all events related to closing the dialogs have finished */ - CloseDialogs(); -#endif - - QMetaObject::invokeMethod(this, "ToggleAlwaysOnTop", Qt::QueuedConnection); -} - -void OBSBasic::ToggleAlwaysOnTop() -{ - bool isAlwaysOnTop = IsAlwaysOnTop(this); - - ui->actionAlwaysOnTop->setChecked(!isAlwaysOnTop); - SetAlwaysOnTop(this, !isAlwaysOnTop); - - show(); -} - -void OBSBasic::GetFPSCommon(uint32_t &num, uint32_t &den) const -{ - const char *val = config_get_string(activeConfiguration, "Video", "FPSCommon"); - - if (strcmp(val, "10") == 0) { - num = 10; - den = 1; - } else if (strcmp(val, "20") == 0) { - num = 20; - den = 1; - } else if (strcmp(val, "24 NTSC") == 0) { - num = 24000; - den = 1001; - } else if (strcmp(val, "25 PAL") == 0) { - num = 25; - den = 1; - } else if (strcmp(val, "29.97") == 0) { - num = 30000; - den = 1001; - } else if (strcmp(val, "48") == 0) { - num = 48; - den = 1; - } else if (strcmp(val, "50 PAL") == 0) { - num = 50; - den = 1; - } else if (strcmp(val, "59.94") == 0) { - num = 60000; - den = 1001; - } else if (strcmp(val, "60") == 0) { - num = 60; - den = 1; - } else { - num = 30; - den = 1; - } -} - -void OBSBasic::GetFPSInteger(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSInt"); - den = 1; -} - -void OBSBasic::GetFPSFraction(uint32_t &num, uint32_t &den) const -{ - num = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNum"); - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSDen"); -} - -void OBSBasic::GetFPSNanoseconds(uint32_t &num, uint32_t &den) const -{ - num = 1000000000; - den = (uint32_t)config_get_uint(activeConfiguration, "Video", "FPSNS"); -} - -void OBSBasic::GetConfigFPS(uint32_t &num, uint32_t &den) const -{ - uint32_t type = config_get_uint(activeConfiguration, "Video", "FPSType"); - - if (type == 1) //"Integer" - GetFPSInteger(num, den); - else if (type == 2) //"Fraction" - GetFPSFraction(num, den); - /* - * else if (false) //"Nanoseconds", currently not implemented - * GetFPSNanoseconds(num, den); - */ - else - GetFPSCommon(num, den); -} - -config_t *OBSBasic::Config() const -{ - return activeConfiguration; -} - #ifdef YOUTUBE_ENABLED YouTubeAppDock *OBSBasic::GetYouTubeAppDock() { @@ -7830,2374 +252,3 @@ void OBSBasic::DeleteYouTubeAppDock() youtubeAppDock = nullptr; } #endif - -void OBSBasic::UpdateEditMenu() -{ - QModelIndexList items = GetAllSelectedSourceItems(); - int totalCount = items.count(); - size_t filter_count = 0; - - if (totalCount == 1) { - OBSSceneItem sceneItem = ui->sources->Get(GetTopSelectedSourceItem()); - OBSSource source = obs_sceneitem_get_source(sceneItem); - filter_count = obs_source_filter_count(source); - } - - bool allowPastingDuplicate = !!clipboard.size(); - for (size_t i = clipboard.size(); i > 0; i--) { - const size_t idx = i - 1; - OBSWeakSource &weak = clipboard[idx].weak_source; - if (obs_weak_source_expired(weak)) { - clipboard.erase(clipboard.begin() + idx); - continue; - } - OBSSourceAutoRelease strong = obs_weak_source_get_source(weak.Get()); - if (allowPastingDuplicate && obs_source_get_output_flags(strong) & OBS_SOURCE_DO_NOT_DUPLICATE) - allowPastingDuplicate = false; - } - - int videoCount = 0; - bool canTransformMultiple = false; - for (int i = 0; i < totalCount; i++) { - OBSSceneItem item = ui->sources->Get(items.value(i).row()); - OBSSource source = obs_sceneitem_get_source(item); - const uint32_t flags = obs_source_get_output_flags(source); - const bool hasVideo = (flags & OBS_SOURCE_VIDEO) != 0; - if (hasVideo && !obs_sceneitem_locked(item)) - canTransformMultiple = true; - - if (hasVideo) - videoCount++; - } - const bool canTransformSingle = videoCount == 1 && totalCount == 1; - - OBSSceneItem curItem = GetCurrentSceneItem(); - bool locked = curItem && obs_sceneitem_locked(curItem); - - ui->actionCopySource->setEnabled(totalCount > 0); - ui->actionEditTransform->setEnabled(canTransformSingle && !locked); - ui->actionCopyTransform->setEnabled(canTransformSingle); - ui->actionPasteTransform->setEnabled(canTransformMultiple && hasCopiedTransform && videoCount > 0); - ui->actionCopyFilters->setEnabled(filter_count > 0); - ui->actionPasteFilters->setEnabled(!obs_weak_source_expired(copyFiltersSource) && totalCount > 0); - ui->actionPasteRef->setEnabled(!!clipboard.size()); - ui->actionPasteDup->setEnabled(allowPastingDuplicate); - - ui->actionMoveUp->setEnabled(totalCount > 0); - ui->actionMoveDown->setEnabled(totalCount > 0); - ui->actionMoveToTop->setEnabled(totalCount > 0); - ui->actionMoveToBottom->setEnabled(totalCount > 0); - - ui->actionResetTransform->setEnabled(canTransformMultiple); - ui->actionRotate90CW->setEnabled(canTransformMultiple); - ui->actionRotate90CCW->setEnabled(canTransformMultiple); - ui->actionRotate180->setEnabled(canTransformMultiple); - ui->actionFlipHorizontal->setEnabled(canTransformMultiple); - ui->actionFlipVertical->setEnabled(canTransformMultiple); - ui->actionFitToScreen->setEnabled(canTransformMultiple); - ui->actionStretchToScreen->setEnabled(canTransformMultiple); - ui->actionCenterToScreen->setEnabled(canTransformMultiple); - ui->actionVerticalCenter->setEnabled(canTransformMultiple); - ui->actionHorizontalCenter->setEnabled(canTransformMultiple); -} - -void OBSBasic::on_actionEditTransform_triggered() -{ - const auto item = GetCurrentSceneItem(); - if (!item) - return; - CreateEditTransformWindow(item); -} - -void OBSBasic::CreateEditTransformWindow(obs_sceneitem_t *item) -{ - if (transformWindow) - transformWindow->close(); - transformWindow = new OBSBasicTransform(item, this); - connect(ui->scenes, &QListWidget::currentItemChanged, transformWindow, &OBSBasicTransform::OnSceneChanged); - transformWindow->show(); - transformWindow->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::on_actionCopyTransform_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - obs_sceneitem_get_info2(item, &copiedTransformInfo); - obs_sceneitem_get_crop(item, &copiedCropInfo); - - ui->actionPasteTransform->setEnabled(true); - hasCopiedTransform = true; -} - -void undo_redo(const std::string &data) -{ - OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str()); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid")); - reinterpret_cast(App()->GetMainWindow())->SetCurrentScene(source.Get(), true); - - obs_scene_load_transform_states(data.c_str()); -} - -void OBSBasic::on_actionPasteTransform_triggered() -{ - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - auto func = [](obs_scene_t *, obs_sceneitem_t *item, void *data) { - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - OBSBasic *main = reinterpret_cast(data); - - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info2(item, &main->copiedTransformInfo); - obs_sceneitem_set_crop(item, &main->copiedCropInfo); - obs_sceneitem_defer_update_end(item); - - return true; - }; - - obs_scene_enum_items(GetCurrentScene(), func, this); - - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Paste").arg(obs_source_get_name(GetCurrentSceneSource())), undo_redo, - undo_redo, undo_data, redo_data); -} - -static bool reset_tr(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, reset_tr, nullptr); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_sceneitem_defer_update_begin(item); - - obs_transform_info info; - vec2_set(&info.pos, 0.0f, 0.0f); - vec2_set(&info.scale, 1.0f, 1.0f); - info.rot = 0.0f; - info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; - info.bounds_type = OBS_BOUNDS_NONE; - info.bounds_alignment = OBS_ALIGN_CENTER; - info.crop_to_bounds = false; - vec2_set(&info.bounds, 0.0f, 0.0f); - obs_sceneitem_set_info2(item, &info); - - obs_sceneitem_crop crop = {}; - obs_sceneitem_set_crop(item, &crop); - - obs_sceneitem_defer_update_end(item); - - return true; -} - -void OBSBasic::on_actionResetTransform_triggered() -{ - OBSScene scene = GetCurrentScene(); - - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(scene, false); - obs_scene_enum_items(scene, reset_tr, nullptr); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(scene, false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.Reset").arg(obs_source_get_name(obs_scene_get_source(scene))), - undo_redo, undo_redo, undo_data, redo_data); - - obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); -} - -static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) -{ - matrix4 boxTransform; - obs_sceneitem_get_box_transform(item, &boxTransform); - - vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); - vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); - - auto GetMinPos = [&](float x, float y) { - vec3 pos; - vec3_set(&pos, x, y, 0.0f); - vec3_transform(&pos, &pos, &boxTransform); - vec3_min(&tl, &tl, &pos); - vec3_max(&br, &br, &pos); - }; - - GetMinPos(0.0f, 0.0f); - GetMinPos(1.0f, 0.0f); - GetMinPos(0.0f, 1.0f); - GetMinPos(1.0f, 1.0f); -} - -static vec3 GetItemTL(obs_sceneitem_t *item) -{ - vec3 tl, br; - GetItemBox(item, tl, br); - return tl; -} - -static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl) -{ - vec3 newTL; - vec2 pos; - - obs_sceneitem_get_pos(item, &pos); - newTL = GetItemTL(item); - pos.x += tl.x - newTL.x; - pos.y += tl.y - newTL.y; - obs_sceneitem_set_pos(item, &pos); -} - -static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, RotateSelectedSources, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - float rot = *reinterpret_cast(param); - - vec3 tl = GetItemTL(item); - - rot += obs_sceneitem_get_rot(item); - if (rot >= 360.0f) - rot -= 360.0f; - else if (rot <= -360.0f) - rot += 360.0f; - obs_sceneitem_set_rot(item, rot); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -}; - -void OBSBasic::on_actionRotate90CW_triggered() -{ - float f90CW = 90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate90CCW_triggered() -{ - float f90CCW = -90.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionRotate180_triggered() -{ - float f180 = 180.0f; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - vec2 &mul = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - vec3 tl = GetItemTL(item); - - vec2 scale; - obs_sceneitem_get_scale(item, &scale); - vec2_mul(&scale, &scale, &mul); - obs_sceneitem_set_scale(item, &scale); - - obs_sceneitem_force_update_transform(item); - - SetItemTL(item, tl); - - return true; -} - -void OBSBasic::on_actionFlipHorizontal_triggered() -{ - vec2 scale; - vec2_set(&scale, -1.0f, 1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionFlipVertical_triggered() -{ - vec2 scale; - vec2_set(&scale, 1.0f, -1.0f); - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param) -{ - obs_bounds_type boundsType = *reinterpret_cast(param); - - if (obs_sceneitem_is_group(item)) - obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param); - if (!obs_sceneitem_selected(item)) - return true; - if (obs_sceneitem_locked(item)) - return true; - - obs_video_info ovi; - obs_get_video_info(&ovi); - - obs_transform_info itemInfo; - vec2_set(&itemInfo.pos, 0.0f, 0.0f); - vec2_set(&itemInfo.scale, 1.0f, 1.0f); - itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; - itemInfo.rot = 0.0f; - - vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height)); - itemInfo.bounds_type = boundsType; - itemInfo.bounds_alignment = OBS_ALIGN_CENTER; - itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item); - - obs_sceneitem_set_info2(item, &itemInfo); - - return true; -} - -void OBSBasic::on_actionFitToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionStretchToScreen_triggered() -{ - obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") - .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::CenterSelectedSceneItems(const CenterType ¢erType) -{ - QModelIndexList selectedItems = GetAllSelectedSourceItems(); - - if (!selectedItems.count()) - return; - - vector items; - - // Filter out items that have no size - for (int x = 0; x < selectedItems.count(); x++) { - OBSSceneItem item = ui->sources->Get(selectedItems[x].row()); - obs_transform_info oti; - obs_sceneitem_get_info2(item, &oti); - - obs_source_t *source = obs_sceneitem_get_source(item); - float width = float(obs_source_get_width(source)) * oti.scale.x; - float height = float(obs_source_get_height(source)) * oti.scale.y; - - if (width == 0.0f || height == 0.0f) - continue; - - items.emplace_back(item); - } - - if (!items.size()) - return; - - // Get center x, y coordinates of items - vec3 center; - - float top = M_INFINITE; - float left = M_INFINITE; - float right = 0.0f; - float bottom = 0.0f; - - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - left = std::min(tl.x, left); - top = std::min(tl.y, top); - right = std::max(br.x, right); - bottom = std::max(br.y, bottom); - } - - center.x = (right + left) / 2.0f; - center.y = (top + bottom) / 2.0f; - center.z = 0.0f; - - // Get coordinates of screen center - obs_video_info ovi; - obs_get_video_info(&ovi); - - vec3 screenCenter; - vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f); - - vec3_mulf(&screenCenter, &screenCenter, 0.5f); - - // Calculate difference between screen center and item center - vec3 offset; - vec3_sub(&offset, &screenCenter, ¢er); - - // Shift items by offset - for (auto &item : items) { - vec3 tl, br; - - GetItemBox(item, tl, br); - - vec3_add(&tl, &tl, &offset); - - vec3 itemTL = GetItemTL(item); - - if (centerType == CenterType::Vertical) - tl.x = itemTL.x; - else if (centerType == CenterType::Horizontal) - tl.y = itemTL.y; - - SetItemTL(item, tl); - } -} - -void OBSBasic::on_actionCenterToScreen_triggered() -{ - CenterType centerType = CenterType::Scene; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionVerticalCenter_triggered() -{ - CenterType centerType = CenterType::Vertical; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::on_actionHorizontalCenter_triggered() -{ - CenterType centerType = CenterType::Horizontal; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - CenterSelectedSceneItems(centerType); - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false); - - std::string undo_data(obs_data_get_json(wrapper)); - std::string redo_data(obs_data_get_json(rwrapper)); - undo_s.add_action( - QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))), - undo_redo, undo_redo, undo_data, redo_data); -} - -void OBSBasic::EnablePreviewDisplay(bool enable) -{ - obs_display_set_enabled(ui->preview->GetDisplay(), enable); - ui->previewContainer->setVisible(enable); - ui->previewDisabledWidget->setVisible(!enable); -} - -void OBSBasic::TogglePreview() -{ - previewEnabled = !previewEnabled; - EnablePreviewDisplay(previewEnabled); -} - -void OBSBasic::EnablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = true; - EnablePreviewDisplay(true); -} - -void OBSBasic::DisablePreview() -{ - if (previewProgramMode) - return; - - previewEnabled = false; - EnablePreviewDisplay(false); -} - -void OBSBasic::EnablePreviewProgram() -{ - SetPreviewProgramMode(true); -} - -void OBSBasic::DisablePreviewProgram() -{ - SetPreviewProgramMode(false); -} - -static bool nudge_callback(obs_scene_t *, obs_sceneitem_t *item, void *param) -{ - if (obs_sceneitem_locked(item)) - return true; - - struct vec2 &offset = *reinterpret_cast(param); - struct vec2 pos; - - if (!obs_sceneitem_selected(item)) { - if (obs_sceneitem_is_group(item)) { - struct vec3 offset3; - vec3_set(&offset3, offset.x, offset.y, 0.0f); - - struct matrix4 matrix; - obs_sceneitem_get_draw_transform(item, &matrix); - vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f); - matrix4_inv(&matrix, &matrix); - vec3_transform(&offset3, &offset3, &matrix); - - struct vec2 new_offset; - vec2_set(&new_offset, offset3.x, offset3.y); - obs_sceneitem_group_enum_items(item, nudge_callback, &new_offset); - } - - return true; - } - - obs_sceneitem_get_pos(item, &pos); - vec2_add(&pos, &pos, &offset); - obs_sceneitem_set_pos(item, &pos); - return true; -} - -void OBSBasic::Nudge(int dist, MoveDir dir) -{ - if (ui->preview->Locked()) - return; - - struct vec2 offset; - vec2_set(&offset, 0.0f, 0.0f); - - switch (dir) { - case MoveDir::Up: - offset.y = (float)-dist; - break; - case MoveDir::Down: - offset.y = (float)dist; - break; - case MoveDir::Left: - offset.x = (float)-dist; - break; - case MoveDir::Right: - offset.x = (float)dist; - break; - } - - if (!recent_nudge) { - recent_nudge = true; - OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string undo_data(obs_data_get_json(wrapper)); - - nudge_timer = new QTimer; - QObject::connect(nudge_timer, &QTimer::timeout, [this, &recent_nudge = recent_nudge, undo_data]() { - OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), true); - std::string redo_data(obs_data_get_json(rwrapper)); - - undo_s.add_action(QTStr("Undo.Transform").arg(obs_source_get_name(GetCurrentSceneSource())), - undo_redo, undo_redo, undo_data, redo_data); - - recent_nudge = false; - }); - connect(nudge_timer, &QTimer::timeout, nudge_timer, &QTimer::deleteLater); - nudge_timer->setSingleShot(true); - } - - if (nudge_timer) { - nudge_timer->stop(); - nudge_timer->start(1000); - } else { - blog(LOG_ERROR, "No nudge timer!"); - } - - obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); -} - -void OBSBasic::DeleteProjector(OBSProjector *projector) -{ - for (size_t i = 0; i < projectors.size(); i++) { - if (projectors[i] == projector) { - projectors[i]->deleteLater(); - projectors.erase(projectors.begin() + i); - break; - } - } -} - -OBSProjector *OBSBasic::OpenProjector(obs_source_t *source, int monitor, ProjectorType type) -{ - /* seriously? 10 monitors? */ - if (monitor > 9 || monitor > QGuiApplication::screens().size() - 1) - return nullptr; - - bool closeProjectors = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CloseExistingProjectors"); - - if (closeProjectors && monitor > -1) { - for (size_t i = projectors.size(); i > 0; i--) { - size_t idx = i - 1; - if (projectors[idx]->GetMonitor() == monitor) - DeleteProjector(projectors[idx]); - } - } - - OBSProjector *projector = new OBSProjector(nullptr, source, monitor, type); - - projectors.emplace_back(projector); - - return projector; -} - -void OBSBasic::OpenStudioProgramProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OpenProjector(obs_sceneitem_get_source(item), monitor, ProjectorType::Source); -} - -void OBSBasic::OpenMultiviewProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OpenProjector(nullptr, monitor, ProjectorType::Multiview); -} - -void OBSBasic::OpenSceneProjector() -{ - int monitor = sender()->property("monitor").toInt(); - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OpenProjector(obs_scene_get_source(scene), monitor, ProjectorType::Scene); -} - -void OBSBasic::OpenStudioProgramWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::StudioProgram); -} - -void OBSBasic::OpenPreviewWindow() -{ - OpenProjector(nullptr, -1, ProjectorType::Preview); -} - -void OBSBasic::OpenSourceWindow() -{ - OBSSceneItem item = GetCurrentSceneItem(); - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - OpenProjector(obs_sceneitem_get_source(item), -1, ProjectorType::Source); -} - -void OBSBasic::OpenSceneWindow() -{ - OBSScene scene = GetCurrentScene(); - if (!scene) - return; - - OBSSource source = obs_scene_get_source(scene); - - OpenProjector(obs_scene_get_source(scene), -1, ProjectorType::Scene); -} - -void OBSBasic::OpenSavedProjectors() -{ - for (SavedProjectorInfo *info : savedProjectorsArray) { - OpenSavedProjector(info); - } -} - -void OBSBasic::OpenSavedProjector(SavedProjectorInfo *info) -{ - if (info) { - OBSProjector *projector = nullptr; - switch (info->type) { - case ProjectorType::Source: - case ProjectorType::Scene: { - OBSSourceAutoRelease source = obs_get_source_by_name(info->name.c_str()); - if (!source) - return; - - projector = OpenProjector(source, info->monitor, info->type); - break; - } - default: { - projector = OpenProjector(nullptr, info->monitor, info->type); - break; - } - } - - if (projector && !info->geometry.empty() && info->monitor < 0) { - QByteArray byteArray = QByteArray::fromBase64(QByteArray(info->geometry.c_str())); - projector->restoreGeometry(byteArray); - - if (!WindowPositionValid(projector->normalGeometry())) { - QRect rect = QGuiApplication::primaryScreen()->geometry(); - projector->setGeometry( - QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), rect)); - } - - if (info->alwaysOnTopOverridden) - projector->SetIsAlwaysOnTop(info->alwaysOnTop, true); - } - } -} - -void OBSBasic::on_actionFullscreenInterface_triggered() -{ - if (!isFullScreen()) - showFullScreen(); - else - showNormal(); -} - -void OBSBasic::UpdateTitleBar() -{ - stringstream name; - - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "Profile"); - const char *sceneCollection = config_get_string(App()->GetUserConfig(), "Basic", "SceneCollection"); - - name << "OBS "; - if (previewProgramMode) - name << "Studio "; - - name << App()->GetVersionString(false); - if (safe_mode) - name << " (" << Str("TitleBar.SafeMode") << ")"; - if (App()->IsPortableMode()) - name << " - " << Str("TitleBar.PortableMode"); - - name << " - " << Str("TitleBar.Profile") << ": " << profile; - name << " - " << Str("TitleBar.Scenes") << ": " << sceneCollection; - - setWindowTitle(QT_UTF8(name.str().c_str())); -} - -int OBSBasic::GetProfilePath(char *path, size_t size, const char *file) const -{ - char profiles_path[512]; - const char *profile = config_get_string(App()->GetUserConfig(), "Basic", "ProfileDir"); - int ret; - - if (!profile) - return -1; - if (!path) - return -1; - if (!file) - file = ""; - - ret = GetAppConfigPath(profiles_path, 512, "obs-studio/basic/profiles"); - if (ret <= 0) - return ret; - - if (!*file) - return snprintf(path, size, "%s/%s", profiles_path, profile); - - return snprintf(path, size, "%s/%s/%s", profiles_path, profile, file); -} - -void OBSBasic::on_resetDocks_triggered(bool force) -{ - /* prune deleted extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - -#ifdef BROWSER_AVAILABLE - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size() || extraBrowserDocks.size()) && - !force) -#else - if ((oldExtraDocks.size() || extraDocks.size() || extraCustomDocks.size()) && !force) -#endif - { - QMessageBox::StandardButton button = - OBSMessageBox::question(this, QTStr("ResetUIWarning.Title"), QTStr("ResetUIWarning.Text")); - - if (button == QMessageBox::No) - return; - } - - /* undock/hide/center extra docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (oldExtraDocks[i]) { - oldExtraDocks[i]->setVisible(true); - oldExtraDocks[i]->setFloating(true); - oldExtraDocks[i]->move(frameGeometry().topLeft() + rect().center() - - oldExtraDocks[i]->rect().center()); - oldExtraDocks[i]->setVisible(false); - } - } - -#define RESET_DOCKLIST(dockList) \ - for (int i = dockList.size() - 1; i >= 0; i--) { \ - dockList[i]->setVisible(true); \ - dockList[i]->setFloating(true); \ - dockList[i]->move(frameGeometry().topLeft() + rect().center() - dockList[i]->rect().center()); \ - dockList[i]->setVisible(false); \ - } - - RESET_DOCKLIST(extraDocks) - RESET_DOCKLIST(extraCustomDocks) -#ifdef BROWSER_AVAILABLE - RESET_DOCKLIST(extraBrowserDocks) -#endif -#undef RESET_DOCKLIST - - restoreState(startingDockLayout); - - int cx = width(); - int cy = height(); - - int cx22_5 = cx * 225 / 1000; - int cx5 = cx * 5 / 100; - int cx21 = cx * 21 / 100; - - cy = cy * 225 / 1000; - - int mixerSize = cx - (cx22_5 * 2 + cx5 + cx21); - - QList docks{ui->scenesDock, ui->sourcesDock, ui->mixerDock, ui->transitionsDock, controlsDock}; - - QList sizes{cx22_5, cx22_5, mixerSize, cx5, cx21}; - - ui->scenesDock->setVisible(true); - ui->sourcesDock->setVisible(true); - ui->mixerDock->setVisible(true); - ui->transitionsDock->setVisible(true); - controlsDock->setVisible(true); - statsDock->setVisible(false); - statsDock->setFloating(true); - - resizeDocks(docks, {cy, cy, cy, cy, cy}, Qt::Vertical); - resizeDocks(docks, sizes, Qt::Horizontal); - - activateWindow(); -} - -void OBSBasic::on_lockDocks_toggled(bool lock) -{ - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - QDockWidget::DockWidgetFeatures mainFeatures = features; - mainFeatures &= ~QDockWidget::QDockWidget::DockWidgetClosable; - - ui->scenesDock->setFeatures(mainFeatures); - ui->sourcesDock->setFeatures(mainFeatures); - ui->mixerDock->setFeatures(mainFeatures); - ui->transitionsDock->setFeatures(mainFeatures); - controlsDock->setFeatures(mainFeatures); - statsDock->setFeatures(features); - - for (int i = extraDocks.size() - 1; i >= 0; i--) - extraDocks[i]->setFeatures(features); - - for (int i = extraCustomDocks.size() - 1; i >= 0; i--) - extraCustomDocks[i]->setFeatures(features); - -#ifdef BROWSER_AVAILABLE - for (int i = extraBrowserDocks.size() - 1; i >= 0; i--) - extraBrowserDocks[i]->setFeatures(features); -#endif - - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } else { - oldExtraDocks[i]->setFeatures(features); - } - } -} - -void OBSBasic::on_sideDocks_toggled(bool side) -{ - if (side) { - setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); - } else { - setCorner(Qt::TopLeftCorner, Qt::TopDockWidgetArea); - setCorner(Qt::TopRightCorner, Qt::TopDockWidgetArea); - setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); - setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); - } -} - -void OBSBasic::on_resetUI_triggered() -{ - on_resetDocks_triggered(); - - ui->toggleListboxToolbars->setChecked(true); - ui->toggleContextBar->setChecked(true); - ui->toggleSourceIcons->setChecked(true); - ui->toggleStatusBar->setChecked(true); - ui->scenes->SetGridMode(false); - ui->actionSceneListMode->setChecked(true); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "gridMode", false); -} - -void OBSBasic::on_multiviewProjectorWindowed_triggered() -{ - OpenProjector(nullptr, -1, ProjectorType::Multiview); -} - -void OBSBasic::on_toggleListboxToolbars_toggled(bool visible) -{ - ui->sourcesToolbar->setVisible(visible); - ui->scenesToolbar->setVisible(visible); - ui->mixerToolbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowListboxToolbars", visible); -} - -void OBSBasic::ShowContextBar() -{ - on_toggleContextBar_toggled(true); - ui->toggleContextBar->setChecked(true); -} - -void OBSBasic::HideContextBar() -{ - on_toggleContextBar_toggled(false); - ui->toggleContextBar->setChecked(false); -} - -void OBSBasic::on_toggleContextBar_toggled(bool visible) -{ - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowContextToolbars", visible); - this->ui->contextContainer->setVisible(visible); - UpdateContextBar(true); -} - -void OBSBasic::on_toggleStatusBar_toggled(bool visible) -{ - ui->statusbar->setVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowStatusBar", visible); -} - -void OBSBasic::on_toggleSourceIcons_toggled(bool visible) -{ - ui->sources->SetIconsVisible(visible); - if (advAudioWindow != nullptr) - advAudioWindow->SetIconsVisible(visible); - - config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible); -} - -void OBSBasic::on_actionLockPreview_triggered() -{ - ui->preview->ToggleLocked(); - ui->actionLockPreview->setChecked(ui->preview->Locked()); -} - -void OBSBasic::on_scalingMenu_aboutToShow() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - QAction *action = ui->actionScaleCanvas; - QString text = QTStr("Basic.MainMenu.Edit.Scale.Canvas"); - text = text.arg(QString::number(ovi.base_width), QString::number(ovi.base_height)); - action->setText(text); - - action = ui->actionScaleOutput; - text = QTStr("Basic.MainMenu.Edit.Scale.Output"); - text = text.arg(QString::number(ovi.output_width), QString::number(ovi.output_height)); - action->setText(text); - action->setVisible(!(ovi.output_width == ovi.base_width && ovi.output_height == ovi.base_height)); - - UpdatePreviewScalingMenu(); -} - -void OBSBasic::on_actionScaleWindow_triggered() -{ - ui->preview->SetFixedScaling(false); - ui->preview->ResetScrollingOffset(); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleCanvas_triggered() -{ - ui->preview->SetFixedScaling(true); - ui->preview->SetScalingLevel(0); - - emit ui->preview->DisplayResized(); -} - -void OBSBasic::on_actionScaleOutput_triggered() -{ - obs_video_info ovi; - obs_get_video_info(&ovi); - - ui->preview->SetFixedScaling(true); - float scalingAmount = float(ovi.output_width) / float(ovi.base_width); - // log base ZOOM_SENSITIVITY of x = log(x) / log(ZOOM_SENSITIVITY) - int32_t approxScalingLevel = int32_t(round(log(scalingAmount) / log(ZOOM_SENSITIVITY))); - ui->preview->SetScalingLevelAndAmount(approxScalingLevel, scalingAmount); - emit ui->preview->DisplayResized(); -} - -void OBSBasic::SetShowing(bool showing) -{ - if (!showing && isVisible()) { - config_set_string(App()->GetUserConfig(), "BasicWindow", "geometry", - saveGeometry().toBase64().constData()); - - /* hide all visible child dialogs */ - visDlgPositions.clear(); - if (!visDialogs.isEmpty()) { - for (QDialog *dlg : visDialogs) { - visDlgPositions.append(dlg->pos()); - dlg->hide(); - } - } - - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Show")); - QTimer::singleShot(0, this, &OBSBasic::hide); - - if (previewEnabled) - EnablePreviewDisplay(false); - -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - - } else if (showing && !isVisible()) { - if (showHide) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - QTimer::singleShot(0, this, &OBSBasic::show); - - if (previewEnabled) - EnablePreviewDisplay(true); - -#ifdef __APPLE__ - EnableOSXDockIcon(true); -#endif - - /* raise and activate window to ensure it is on top */ - raise(); - activateWindow(); - - /* show all child dialogs that was visible earlier */ - if (!visDialogs.isEmpty()) { - for (int i = 0; i < visDialogs.size(); ++i) { - QDialog *dlg = visDialogs[i]; - dlg->move(visDlgPositions[i]); - dlg->show(); - } - } - - /* Unminimize window if it was hidden to tray instead of task - * bar. */ - if (sysTrayMinimizeToTray()) { - Qt::WindowStates state; - state = windowState() & ~Qt::WindowMinimized; - state |= Qt::WindowActive; - setWindowState(state); - } - } -} - -void OBSBasic::ToggleShowHide() -{ - bool showing = isVisible(); - if (showing) { - /* check for modal dialogs */ - EnumDialogs(); - if (!modalDialogs.isEmpty() || !visMsgBoxes.isEmpty()) - return; - } - SetShowing(!showing); -} - -void OBSBasic::SystemTrayInit() -{ -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs.png"); -#endif - trayIcon.reset(new QSystemTrayIcon(QIcon::fromTheme("obs-tray", trayIconFile), this)); - trayIcon->setToolTip("OBS Studio"); - - showHide = new QAction(QTStr("Basic.SystemTray.Show"), trayIcon.data()); - sysTrayStream = - new QAction(StreamingActive() ? QTStr("Basic.Main.StopStreaming") : QTStr("Basic.Main.StartStreaming"), - trayIcon.data()); - sysTrayRecord = - new QAction(RecordingActive() ? QTStr("Basic.Main.StopRecording") : QTStr("Basic.Main.StartRecording"), - trayIcon.data()); - sysTrayReplayBuffer = new QAction(ReplayBufferActive() ? QTStr("Basic.Main.StopReplayBuffer") - : QTStr("Basic.Main.StartReplayBuffer"), - trayIcon.data()); - sysTrayVirtualCam = new QAction(VirtualCamActive() ? QTStr("Basic.Main.StopVirtualCam") - : QTStr("Basic.Main.StartVirtualCam"), - trayIcon.data()); - exit = new QAction(QTStr("Exit"), trayIcon.data()); - - trayMenu = new QMenu; - previewProjector = new QMenu(QTStr("PreviewProjector")); - studioProgramProjector = new QMenu(QTStr("StudioProgramProjector")); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - trayMenu->addAction(showHide); - trayMenu->addSeparator(); - trayMenu->addMenu(previewProjector); - trayMenu->addMenu(studioProgramProjector); - trayMenu->addSeparator(); - trayMenu->addAction(sysTrayStream); - trayMenu->addAction(sysTrayRecord); - trayMenu->addAction(sysTrayReplayBuffer); - trayMenu->addAction(sysTrayVirtualCam); - trayMenu->addSeparator(); - trayMenu->addAction(exit); - trayIcon->setContextMenu(trayMenu); - trayIcon->show(); - - if (outputHandler && !outputHandler->replayBuffer) - sysTrayReplayBuffer->setEnabled(false); - - sysTrayVirtualCam->setEnabled(vcamEnabled); - - if (Active()) - OnActivate(true); - - connect(trayIcon.data(), &QSystemTrayIcon::activated, this, &OBSBasic::IconActivated); - connect(showHide, &QAction::triggered, this, &OBSBasic::ToggleShowHide); - connect(sysTrayStream, &QAction::triggered, this, &OBSBasic::StreamActionTriggered); - connect(sysTrayRecord, &QAction::triggered, this, &OBSBasic::RecordActionTriggered); - connect(sysTrayReplayBuffer.data(), &QAction::triggered, this, &OBSBasic::ReplayBufferActionTriggered); - connect(sysTrayVirtualCam.data(), &QAction::triggered, this, &OBSBasic::VirtualCamActionTriggered); - connect(exit, &QAction::triggered, this, &OBSBasic::close); -} - -void OBSBasic::IconActivated(QSystemTrayIcon::ActivationReason reason) -{ - // Refresh projector list - previewProjector->clear(); - studioProgramProjector->clear(); - AddProjectorMenuMonitors(previewProjector, this, &OBSBasic::OpenPreviewProjector); - AddProjectorMenuMonitors(studioProgramProjector, this, &OBSBasic::OpenStudioProgramProjector); - -#ifdef __APPLE__ - UNUSED_PARAMETER(reason); -#else - if (reason == QSystemTrayIcon::Trigger) { - EnablePreviewDisplay(previewEnabled && !isVisible()); - ToggleShowHide(); - } -#endif -} - -void OBSBasic::SysTrayNotify(const QString &text, QSystemTrayIcon::MessageIcon n) -{ - if (trayIcon && trayIcon->isVisible() && QSystemTrayIcon::supportsMessages()) { - QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::MessageIcon(n); - trayIcon->showMessage("OBS Studio", text, icon, 10000); - } -} - -void OBSBasic::SystemTray(bool firstStarted) -{ - if (!QSystemTrayIcon::isSystemTrayAvailable()) - return; - if (!trayIcon && !firstStarted) - return; - - bool sysTrayWhenStarted = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayWhenStarted"); - bool sysTrayEnabled = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayEnabled"); - - if (firstStarted) - SystemTrayInit(); - - if (!sysTrayEnabled) { - trayIcon->hide(); - } else { - trayIcon->show(); - if (firstStarted && (sysTrayWhenStarted || opt_minimize_tray)) { - EnablePreviewDisplay(false); -#ifdef __APPLE__ - EnableOSXDockIcon(false); -#endif - opt_minimize_tray = false; - } - } - - if (isVisible()) - showHide->setText(QTStr("Basic.SystemTray.Hide")); - else - showHide->setText(QTStr("Basic.SystemTray.Show")); -} - -bool OBSBasic::sysTrayMinimizeToTray() -{ - return config_get_bool(App()->GetUserConfig(), "BasicWindow", "SysTrayMinimizeToTray"); -} - -void OBSBasic::on_actionMainUndo_triggered() -{ - undo_s.undo(); -} - -void OBSBasic::on_actionMainRedo_triggered() -{ - undo_s.redo(); -} - -void OBSBasic::on_actionCopySource_triggered() -{ - clipboard.clear(); - - for (auto &selectedSource : GetAllSelectedSourceItems()) { - OBSSceneItem item = ui->sources->Get(selectedSource.row()); - if (!item) - continue; - - OBSSource source = obs_sceneitem_get_source(item); - - SourceCopyInfo copyInfo; - copyInfo.weak_source = OBSGetWeakRef(source); - obs_sceneitem_get_info2(item, ©Info.transform); - obs_sceneitem_get_crop(item, ©Info.crop); - copyInfo.blend_method = obs_sceneitem_get_blending_method(item); - copyInfo.blend_mode = obs_sceneitem_get_blending_mode(item); - copyInfo.visible = obs_sceneitem_visible(item); - - clipboard.push_back(copyInfo); - } - - UpdateEditMenu(); -} - -void OBSBasic::on_actionPasteRef_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - OBSScene scene = GetCurrentScene(); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - - OBSSource source = OBSGetStrongRef(copyInfo.weak_source); - if (!source) - continue; - - const char *name = obs_source_get_name(source); - - /* do not allow duplicate refs of the same group in the same - * scene */ - if (!!obs_scene_get_group(scene, name)) { - continue; - } - - OBSBasicSourceSelect::SourcePaste(copyInfo, false); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSourceRef"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::on_actionPasteDup_triggered() -{ - OBSSource scene_source = GetCurrentSceneSource(); - OBSData undo_data = BackupScene(scene_source); - - undo_s.push_disabled(); - - for (size_t i = clipboard.size(); i > 0; i--) { - SourceCopyInfo ©Info = clipboard[i - 1]; - OBSBasicSourceSelect::SourcePaste(copyInfo, true); - } - - undo_s.pop_disabled(); - - QString action_name = QTStr("Undo.PasteSource"); - const char *scene_name = obs_source_get_name(scene_source); - - OBSData redo_data = BackupScene(scene_source); - CreateSceneUndoRedoAction(action_name.arg(scene_name), undo_data, redo_data); -} - -void OBSBasic::SourcePasteFilters(OBSSource source, OBSSource dstSource) -{ - if (source == dstSource) - return; - - OBSDataArrayAutoRelease undo_array = obs_source_backup_filters(dstSource); - obs_source_copy_filters(dstSource, source); - OBSDataArrayAutoRelease redo_array = obs_source_backup_filters(dstSource); - - const char *srcName = obs_source_get_name(source); - const char *dstName = obs_source_get_name(dstSource); - QString text = QTStr("Undo.Filters.Paste.Multiple").arg(srcName, dstName); - - CreateFilterPasteUndoRedoAction(text, dstSource, undo_array, redo_array); -} - -void OBSBasic::AudioMixerCopyFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *source = vol->GetSource(); - - copyFiltersSource = obs_source_get_weak_source(source); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::AudioMixerPasteFilters() -{ - QAction *action = reinterpret_cast(sender()); - VolControl *vol = action->property("volControl").value(); - obs_source_t *dstSource = vol->GetSource(); - - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::SceneCopyFilters() -{ - copyFiltersSource = obs_source_get_weak_source(GetCurrentSceneSource()); - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::ScenePasteFilters() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSource dstSource = GetCurrentSceneSource(); - - SourcePasteFilters(source.Get(), dstSource); -} - -void OBSBasic::on_actionCopyFilters_triggered() -{ - OBSSceneItem item = GetCurrentSceneItem(); - - if (!item) - return; - - OBSSource source = obs_sceneitem_get_source(item); - - copyFiltersSource = obs_source_get_weak_source(source); - - ui->actionPasteFilters->setEnabled(true); -} - -void OBSBasic::CreateFilterPasteUndoRedoAction(const QString &text, obs_source_t *source, obs_data_array_t *undo_array, - obs_data_array_t *redo_array) -{ - auto undo_redo = [this](const std::string &json) { - OBSDataAutoRelease data = obs_data_create_from_json(json.c_str()); - OBSDataArrayAutoRelease array = obs_data_get_array(data, "array"); - OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(data, "uuid")); - - obs_source_restore_filters(source, array); - - if (filters) - filters->UpdateSource(source); - }; - - const char *uuid = obs_source_get_uuid(source); - - OBSDataAutoRelease undo_data = obs_data_create(); - OBSDataAutoRelease redo_data = obs_data_create(); - obs_data_set_array(undo_data, "array", undo_array); - obs_data_set_array(redo_data, "array", redo_array); - obs_data_set_string(undo_data, "uuid", uuid); - obs_data_set_string(redo_data, "uuid", uuid); - - undo_s.add_action(text, undo_redo, undo_redo, obs_data_get_json(undo_data), obs_data_get_json(redo_data)); -} - -void OBSBasic::on_actionPasteFilters_triggered() -{ - OBSSourceAutoRelease source = obs_weak_source_get_source(copyFiltersSource); - - OBSSceneItem sceneItem = GetCurrentSceneItem(); - OBSSource dstSource = obs_sceneitem_get_source(sceneItem); - - SourcePasteFilters(source.Get(), dstSource); -} - -static void ConfirmColor(SourceTree *sources, const QColor &color, QModelIndexList selectedItems) -{ - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", 1); - obs_data_set_string(privData, "color", QT_TO_UTF8(color.name(QColor::HexArgb))); - } -} - -void OBSBasic::ColorChange() -{ - QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes(); - QAction *action = qobject_cast(sender()); - QPushButton *colorButton = qobject_cast(sender()); - - if (selectedItems.count() == 0) - return; - - if (colorButton) { - int preset = colorButton->property("bgColor").value(); - - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet(""); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset + 1); - obs_data_set_string(privData, "color", ""); - } - - for (int i = 1; i < 9; i++) { - stringstream button; - button << "preset" << i; - QPushButton *cButton = - colorButton->parentWidget()->findChild(button.str().c_str()); - cButton->setStyleSheet("border: 1px solid black"); - } - - colorButton->setStyleSheet("border: 2px solid black"); - } else if (action) { - int preset = action->property("bgColor").value(); - - if (preset == 1) { - OBSSceneItem curSceneItem = GetCurrentSceneItem(); - SourceTreeItem *curTreeItem = GetItemWidgetFromSceneItem(curSceneItem); - OBSDataAutoRelease curPrivData = obs_sceneitem_get_private_settings(curSceneItem); - - int oldPreset = obs_data_get_int(curPrivData, "color-preset"); - const QString oldSheet = curTreeItem->styleSheet(); - - auto liveChangeColor = [=](const QColor &color) { - if (color.isValid()) { - curTreeItem->setStyleSheet("background: " + color.name(QColor::HexArgb)); - } - }; - - auto changedColor = [=](const QColor &color) { - if (color.isValid()) { - ConfirmColor(ui->sources, color, selectedItems); - } - }; - - auto rejected = [=]() { - if (oldPreset == 1) { - curTreeItem->setStyleSheet(oldSheet); - curTreeItem->setProperty("bgColor", 0); - } else if (oldPreset == 0) { - curTreeItem->setStyleSheet("background: none"); - curTreeItem->setProperty("bgColor", 0); - } else { - curTreeItem->setStyleSheet(""); - curTreeItem->setProperty("bgColor", oldPreset - 1); - } - - curTreeItem->style()->unpolish(curTreeItem); - curTreeItem->style()->polish(curTreeItem); - }; - - QColorDialog::ColorDialogOptions options = QColorDialog::ShowAlphaChannel; - - const char *oldColor = obs_data_get_string(curPrivData, "color"); - const char *customColor = *oldColor != 0 ? oldColor : "#55FF0000"; -#ifdef __linux__ - // TODO: Revisit hang on Ubuntu with native dialog - options |= QColorDialog::DontUseNativeDialog; -#endif - - QColorDialog *colorDialog = new QColorDialog(this); - colorDialog->setOptions(options); - colorDialog->setCurrentColor(QColor(customColor)); - connect(colorDialog, &QColorDialog::currentColorChanged, liveChangeColor); - connect(colorDialog, &QColorDialog::colorSelected, changedColor); - connect(colorDialog, &QColorDialog::rejected, rejected); - colorDialog->open(); - } else { - for (int x = 0; x < selectedItems.count(); x++) { - SourceTreeItem *treeItem = ui->sources->GetItemWidget(selectedItems[x].row()); - treeItem->setStyleSheet("background: none"); - treeItem->setProperty("bgColor", preset); - treeItem->style()->unpolish(treeItem); - treeItem->style()->polish(treeItem); - - OBSSceneItem sceneItem = ui->sources->Get(selectedItems[x].row()); - OBSDataAutoRelease privData = obs_sceneitem_get_private_settings(sceneItem); - obs_data_set_int(privData, "color-preset", preset); - obs_data_set_string(privData, "color", ""); - } - } - } -} - -SourceTreeItem *OBSBasic::GetItemWidgetFromSceneItem(obs_sceneitem_t *sceneItem) -{ - int i = 0; - SourceTreeItem *treeItem = ui->sources->GetItemWidget(i); - OBSSceneItem item = ui->sources->Get(i); - int64_t id = obs_sceneitem_get_id(sceneItem); - while (treeItem && obs_sceneitem_get_id(item) != id) { - i++; - treeItem = ui->sources->GetItemWidget(i); - item = ui->sources->Get(i); - } - if (treeItem) - return treeItem; - - return nullptr; -} - -void OBSBasic::on_autoConfigure_triggered() -{ - AutoConfig test(this); - test.setModal(true); - test.show(); - test.exec(); -} - -void OBSBasic::on_stats_triggered() -{ - if (!stats.isNull()) { - stats->show(); - stats->raise(); - return; - } - - OBSBasicStats *statsDlg; - statsDlg = new OBSBasicStats(nullptr); - statsDlg->show(); - stats = statsDlg; -} - -void OBSBasic::on_actionShowAbout_triggered() -{ - if (about) - about->close(); - - about = new OBSAbout(this); - about->show(); - - about->setAttribute(Qt::WA_DeleteOnClose, true); -} - -void OBSBasic::ResizeOutputSizeOfSource() -{ - if (obs_video_active()) - return; - - QMessageBox resize_output(this); - resize_output.setText(QTStr("ResizeOutputSizeOfSource.Text") + "\n\n" + - QTStr("ResizeOutputSizeOfSource.Continue")); - QAbstractButton *Yes = resize_output.addButton(QTStr("Yes"), QMessageBox::YesRole); - resize_output.addButton(QTStr("No"), QMessageBox::NoRole); - resize_output.setIcon(QMessageBox::Warning); - resize_output.setWindowTitle(QTStr("ResizeOutputSizeOfSource")); - resize_output.exec(); - - if (resize_output.clickedButton() != Yes) - return; - - OBSSource source = obs_sceneitem_get_source(GetCurrentSceneItem()); - - int width = obs_source_get_width(source); - int height = obs_source_get_height(source); - - config_set_uint(activeConfiguration, "Video", "BaseCX", width); - config_set_uint(activeConfiguration, "Video", "BaseCY", height); - config_set_uint(activeConfiguration, "Video", "OutputCX", width); - config_set_uint(activeConfiguration, "Video", "OutputCY", height); - - ResetVideo(); - ResetOutputs(); - activeConfiguration.SaveSafe("tmp"); - on_actionFitToScreen_triggered(); -} - -QAction *OBSBasic::AddDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairOldExtraDockName); - -#ifdef BROWSER_AVAILABLE - QAction *action = new QAction(dock->windowTitle(), ui->menuDocks); - - if (!extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, action); - else - ui->menuDocks->addAction(action); -#else - QAction *action = ui->menuDocks->addAction(dock->windowTitle()); -#endif - action->setCheckable(true); - assignDockToggle(dock, action); - oldExtraDocks.push_back(dock); - oldExtraDockNames.push_back(dock->objectName()); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - - /* prune deleted docks */ - for (int i = oldExtraDocks.size() - 1; i >= 0; i--) { - if (!oldExtraDocks[i]) { - oldExtraDocks.removeAt(i); - oldExtraDockNames.removeAt(i); - } - } - - return action; -} - -void OBSBasic::RepairOldExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = oldExtraDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The dock '%s' got its object name restored", QT_TO_UTF8(oldExtraDockNames[idx])); - - dock->setObjectName(oldExtraDockNames[idx]); -} - -void OBSBasic::AddDockWidget(QDockWidget *dock, Qt::DockWidgetArea area, bool extraBrowser) -{ - if (dock->objectName().isEmpty()) - return; - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - setupDockAction(dock); - dock->setFeatures(features); - addDockWidget(area, dock); - -#ifdef BROWSER_AVAILABLE - if (extraBrowser && extraBrowserMenuDocksSeparator.isNull()) - extraBrowserMenuDocksSeparator = ui->menuDocks->addSeparator(); - - if (!extraBrowser && !extraBrowserMenuDocksSeparator.isNull()) - ui->menuDocks->insertAction(extraBrowserMenuDocksSeparator, dock->toggleViewAction()); - else - ui->menuDocks->addAction(dock->toggleViewAction()); - - if (extraBrowser) - return; -#else - UNUSED_PARAMETER(extraBrowser); - - ui->menuDocks->addAction(dock->toggleViewAction()); -#endif - - extraDockNames.push_back(dock->objectName()); - extraDocks.push_back(std::shared_ptr(dock)); -} - -void OBSBasic::RemoveDockWidget(const QString &name) -{ - if (extraDockNames.contains(name)) { - int idx = extraDockNames.indexOf(name); - extraDockNames.removeAt(idx); - extraDocks[idx].reset(); - extraDocks.removeAt(idx); - } else if (extraCustomDockNames.contains(name)) { - int idx = extraCustomDockNames.indexOf(name); - extraCustomDockNames.removeAt(idx); - removeDockWidget(extraCustomDocks[idx]); - extraCustomDocks.removeAt(idx); - } -} - -bool OBSBasic::IsDockObjectNameUsed(const QString &name) -{ - QStringList list; - list << "scenesDock" - << "sourcesDock" - << "mixerDock" - << "transitionsDock" - << "controlsDock" - << "statsDock"; - list << oldExtraDockNames; - list << extraDockNames; - list << extraCustomDockNames; - - return list.contains(name); -} - -void OBSBasic::AddCustomDockWidget(QDockWidget *dock) -{ - // Prevent the object name from being changed - connect(dock, &QObject::objectNameChanged, this, &OBSBasic::RepairCustomExtraDockName); - - bool lock = ui->lockDocks->isChecked(); - QDockWidget::DockWidgetFeatures features = - lock ? QDockWidget::NoDockWidgetFeatures - : (QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | - QDockWidget::DockWidgetFloatable); - - dock->setFeatures(features); - addDockWidget(Qt::RightDockWidgetArea, dock); - - extraCustomDockNames.push_back(dock->objectName()); - extraCustomDocks.push_back(dock); -} - -void OBSBasic::RepairCustomExtraDockName() -{ - QDockWidget *dock = reinterpret_cast(sender()); - int idx = extraCustomDocks.indexOf(dock); - QSignalBlocker block(dock); - - if (idx == -1) { - blog(LOG_WARNING, "A custom dock got its object name changed"); - return; - } - - blog(LOG_WARNING, "The custom dock '%s' got its object name restored", QT_TO_UTF8(extraCustomDockNames[idx])); - - dock->setObjectName(extraCustomDockNames[idx]); -} - -OBSBasic *OBSBasic::Get() -{ - return reinterpret_cast(App()->GetMainWindow()); -} - -bool OBSBasic::StreamingActive() -{ - if (!outputHandler) - return false; - return outputHandler->StreamingActive(); -} - -bool OBSBasic::RecordingActive() -{ - if (!outputHandler) - return false; - return outputHandler->RecordingActive(); -} - -bool OBSBasic::ReplayBufferActive() -{ - if (!outputHandler) - return false; - return outputHandler->ReplayBufferActive(); -} - -bool OBSBasic::VirtualCamActive() -{ - if (!outputHandler) - return false; - return outputHandler->VirtualCamActive(); -} - -SceneRenameDelegate::SceneRenameDelegate(QObject *parent) : QStyledItemDelegate(parent) {} - -void SceneRenameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const -{ - QStyledItemDelegate::setEditorData(editor, index); - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->selectAll(); -} - -bool SceneRenameDelegate::eventFilter(QObject *editor, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QKeyEvent *keyEvent = static_cast(event); - switch (keyEvent->key()) { - case Qt::Key_Escape: { - QLineEdit *lineEdit = qobject_cast(editor); - if (lineEdit) - lineEdit->undo(); - break; - } - case Qt::Key_Tab: - case Qt::Key_Backtab: - return false; - } - } - - return QStyledItemDelegate::eventFilter(editor, event); -} - -void OBSBasic::UpdatePatronJson(const QString &text, const QString &error) -{ - if (!error.isEmpty()) - return; - - patronJson = QT_TO_UTF8(text); -} - -void OBSBasic::PauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, true)) { - os_atomic_set_bool(&recording_paused, true); - - emit RecordingPaused(); - - ui->statusbar->RecordingPaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusPaused); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/obs_paused_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/obs_paused.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-paused", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_PAUSED); - - if (os_atomic_load_bool(&replaybuf_active)) - ShowReplayBufferPauseWarning(); - } -} - -void OBSBasic::UnpauseRecording() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput || - !os_atomic_load_bool(&recording_paused)) - return; - - obs_output_t *output = outputHandler->fileOutput; - - if (obs_output_pause(output, false)) { - os_atomic_set_bool(&recording_paused, false); - - emit RecordingUnpaused(); - - ui->statusbar->RecordingUnpaused(); - - TaskbarOverlaySetStatus(TaskbarOverlayStatusActive); - if (trayIcon && trayIcon->isVisible()) { -#ifdef __APPLE__ - QIcon trayIconFile = QIcon(":/res/images/tray_active_macos.svg"); - trayIconFile.setIsMask(true); -#else - QIcon trayIconFile = QIcon(":/res/images/tray_active.png"); -#endif - trayIcon->setIcon(QIcon::fromTheme("obs-tray-active", trayIconFile)); - } - - OnEvent(OBS_FRONTEND_EVENT_RECORDING_UNPAUSED); - } -} - -void OBSBasic::RecordPauseToggled() -{ - if (!isRecordingPausable || !outputHandler || !outputHandler->fileOutput) - return; - - obs_output_t *output = outputHandler->fileOutput; - bool enable = !obs_output_paused(output); - - if (enable) - PauseRecording(); - else - UnpauseRecording(); -} - -void OBSBasic::UpdateIsRecordingPausable() -{ - const char *mode = config_get_string(activeConfiguration, "Output", "Mode"); - bool adv = astrcmpi(mode, "Advanced") == 0; - bool shared = true; - - if (adv) { - const char *recType = config_get_string(activeConfiguration, "AdvOut", "RecType"); - - if (astrcmpi(recType, "FFmpeg") == 0) { - shared = config_get_bool(activeConfiguration, "AdvOut", "FFOutputToFile"); - } else { - const char *recordEncoder = config_get_string(activeConfiguration, "AdvOut", "RecEncoder"); - shared = astrcmpi(recordEncoder, "none") == 0; - } - } else { - const char *quality = config_get_string(activeConfiguration, "SimpleOutput", "RecQuality"); - shared = strcmp(quality, "Stream") == 0; - } - - isRecordingPausable = !shared; -} - -#define MBYTE (1024ULL * 1024ULL) -#define MBYTES_LEFT_STOP_REC 50ULL -#define MAX_BYTES_LEFT (MBYTES_LEFT_STOP_REC * MBYTE) - -const char *OBSBasic::GetCurrentOutputPath() -{ - const char *path = nullptr; - const char *mode = config_get_string(Config(), "Output", "Mode"); - - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - - if (strcmp(advanced_mode, "FFmpeg") == 0) { - path = config_get_string(Config(), "AdvOut", "FFFilePath"); - } else { - path = config_get_string(Config(), "AdvOut", "RecFilePath"); - } - } else { - path = config_get_string(Config(), "SimpleOutput", "FilePath"); - } - - return path; -} - -void OBSBasic::OutputPathInvalidMessage() -{ - blog(LOG_ERROR, "Recording stopped because of bad output path"); - - OBSMessageBox::critical(this, QTStr("Output.BadPath.Title"), QTStr("Output.BadPath.Text")); -} - -bool OBSBasic::IsFFmpegOutputToURL() const -{ - const char *mode = config_get_string(Config(), "Output", "Mode"); - if (strcmp(mode, "Advanced") == 0) { - const char *advanced_mode = config_get_string(Config(), "AdvOut", "RecType"); - if (strcmp(advanced_mode, "FFmpeg") == 0) { - bool is_local = config_get_bool(Config(), "AdvOut", "FFOutputToFile"); - if (!is_local) - return true; - } - } - - return false; -} - -bool OBSBasic::OutputPathValid() -{ - if (IsFFmpegOutputToURL()) - return true; - - const char *path = GetCurrentOutputPath(); - return path && *path && QDir(path).exists(); -} - -void OBSBasic::DiskSpaceMessage() -{ - blog(LOG_ERROR, "Recording stopped because of low disk space"); - - OBSMessageBox::critical(this, QTStr("Output.RecordNoSpace.Title"), QTStr("Output.RecordNoSpace.Msg")); -} - -bool OBSBasic::LowDiskSpace() -{ - const char *path; - - path = GetCurrentOutputPath(); - if (!path) - return false; - - uint64_t num_bytes = os_get_free_disk_space(path); - - if (num_bytes < (MAX_BYTES_LEFT)) - return true; - else - return false; -} - -void OBSBasic::CheckDiskSpaceRemaining() -{ - if (LowDiskSpace()) { - StopRecording(); - StopReplayBuffer(); - - DiskSpaceMessage(); - } -} - -void OBSBasic::ResetStatsHotkey() -{ - const QList list = findChildren(); - - for (OBSBasicStats *s : list) { - s->Reset(); - } -} - -void OBSBasic::on_OBSBasic_customContextMenuRequested(const QPoint &pos) -{ - QWidget *widget = childAt(pos); - const char *className = nullptr; - QString objName; - if (widget != nullptr) { - className = widget->metaObject()->className(); - objName = widget->objectName(); - } - - QPoint globalPos = mapToGlobal(pos); - if (className && strstr(className, "Dock") != nullptr && !objName.isEmpty()) { - if (objName.compare("scenesDock") == 0) { - ui->scenes->customContextMenuRequested(globalPos); - } else if (objName.compare("sourcesDock") == 0) { - ui->sources->customContextMenuRequested(globalPos); - } else if (objName.compare("mixerDock") == 0) { - StackedMixerAreaContextMenuRequested(); - } - } else if (!className) { - ui->menuDocks->exec(globalPos); - } -} - -void OBSBasic::UpdateProjectorHideCursor() -{ - for (size_t i = 0; i < projectors.size(); i++) - projectors[i]->SetHideCursor(); -} - -void OBSBasic::UpdateProjectorAlwaysOnTop(bool top) -{ - for (size_t i = 0; i < projectors.size(); i++) - SetAlwaysOnTop(projectors[i], top); -} - -void OBSBasic::ResetProjectors() -{ - OBSDataArrayAutoRelease savedProjectorList = SaveProjectors(); - ClearProjectors(); - LoadSavedProjectors(savedProjectorList); - OpenSavedProjectors(); -} - -void OBSBasic::on_sourcePropertiesButton_clicked() -{ - on_actionSourceProperties_triggered(); -} - -void OBSBasic::on_sourceFiltersButton_clicked() -{ - OpenFilters(); -} - -void OBSBasic::on_actionSceneFilters_triggered() -{ - OBSSource sceneSource = GetCurrentSceneSource(); - - if (sceneSource) - OpenFilters(sceneSource); -} - -void OBSBasic::on_sourceInteractButton_clicked() -{ - on_actionInteract_triggered(); -} - -void OBSBasic::ShowStatusBarMessage(const QString &message) -{ - ui->statusbar->clearMessage(); - ui->statusbar->showMessage(message, 10000); -} - -void OBSBasic::UpdatePreviewSafeAreas() -{ - drawSafeAreas = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ShowSafeAreas"); -} - -void OBSBasic::UpdatePreviewOverflowSettings() -{ - bool hidden = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowHidden"); - bool select = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowSelectionHidden"); - bool always = config_get_bool(App()->GetUserConfig(), "BasicWindow", "OverflowAlwaysVisible"); - - ui->preview->SetOverflowHidden(hidden); - ui->preview->SetOverflowSelectionHidden(select); - ui->preview->SetOverflowAlwaysVisible(always); -} - -void OBSBasic::SetDisplayAffinity(QWindow *window) -{ - if (!SetDisplayAffinitySupported()) - return; - - bool hideFromCapture = config_get_bool(App()->GetUserConfig(), "BasicWindow", "HideOBSWindowsFromCapture"); - - // Don't hide projectors, those are designed to be visible / captured - if (window->property("isOBSProjectorWindow") == true) - return; - -#ifdef _WIN32 - HWND hwnd = (HWND)window->winId(); - - DWORD curAffinity; - if (GetWindowDisplayAffinity(hwnd, &curAffinity)) { - if (hideFromCapture && curAffinity != WDA_EXCLUDEFROMCAPTURE) - SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE); - else if (!hideFromCapture && curAffinity != WDA_NONE) - SetWindowDisplayAffinity(hwnd, WDA_NONE); - } - -#else - // TODO: Implement for other platforms if possible. Don't forget to - // implement SetDisplayAffinitySupported too! - UNUSED_PARAMETER(hideFromCapture); -#endif -} - -static inline QColor color_from_int(long long val) -{ - return QColor(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); -} - -QColor OBSBasic::GetSelectionColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectRed")); - } else { - return QColor::fromRgb(255, 0, 0); - } -} - -QColor OBSBasic::GetCropColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectGreen")); - } else { - return QColor::fromRgb(0, 255, 0); - } -} - -QColor OBSBasic::GetHoverColor() const -{ - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - return color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "SelectBlue")); - } else { - return QColor::fromRgb(0, 127, 255); - } -} - -void OBSBasic::UpdatePreviewSpacingHelpers() -{ - drawSpacingHelpers = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SpacingHelpersEnabled"); -} - -float OBSBasic::GetDevicePixelRatio() -{ - return dpi; -} - -void OBSBasic::OnEvent(enum obs_frontend_event event) -{ - if (api) - api->on_event(event); -} - -void OBSBasic::UpdatePreviewScrollbars() -{ - if (!ui->preview->IsFixedScaling()) { - ui->previewXScrollBar->setRange(0, 0); - ui->previewYScrollBar->setRange(0, 0); - } -} - -void OBSBasic::on_previewXScrollBar_valueChanged(int value) -{ - emit PreviewXScrollBarMoved(value); -} - -void OBSBasic::on_previewYScrollBar_valueChanged(int value) -{ - emit PreviewYScrollBarMoved(value); -} - -void OBSBasic::PreviewScalingModeChanged(int value) -{ - switch (value) { - case 0: - on_actionScaleWindow_triggered(); - break; - case 1: - on_actionScaleCanvas_triggered(); - break; - case 2: - on_actionScaleOutput_triggered(); - break; - }; -} - -// MARK: - Generic UI Helper Functions - -OBSPromptResult OBSBasic::PromptForName(const OBSPromptRequest &request, const OBSPromptCallback &callback) -{ - OBSPromptResult result; - - for (;;) { - result.success = false; - - if (request.withOption && !request.optionPrompt.empty()) { - result.optionValue = request.optionValue; - - result.success = NameDialog::AskForNameWithOption( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - request.optionPrompt.c_str(), result.optionValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - - } else { - result.success = NameDialog::AskForName( - this, request.title.c_str(), request.prompt.c_str(), result.promptValue, - (request.promptValue.empty() ? nullptr : request.promptValue.c_str())); - } - - if (!result.success) { - break; - } - - if (result.promptValue.empty()) { - OBSMessageBox::warning(this, QTStr("NoNameEntered.Title"), QTStr("NoNameEntered.Text")); - continue; - } - - if (!callback(result)) { - OBSMessageBox::warning(this, QTStr("NameExists.Title"), QTStr("NameExists.Text")); - continue; - } - - break; - } - - return result; -} diff --git a/frontend/widgets/OBSQTDisplay.cpp b/frontend/widgets/OBSQTDisplay.cpp index f7c37ae9a..4f0d81fb3 100644 --- a/frontend/widgets/OBSQTDisplay.cpp +++ b/frontend/widgets/OBSQTDisplay.cpp @@ -1,57 +1,24 @@ -#include "moc_qt-display.cpp" -#include "display-helpers.hpp" -#include -#include -#include -#include +#include "OBSQTDisplay.hpp" -#include -#include +#include +#include + +#if !defined(_WIN32) && !defined(__APPLE__) +#include +#endif + +#include +#ifdef ENABLE_WAYLAND +#include +#include +#endif #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #endif -#if !defined(_WIN32) && !defined(__APPLE__) -#include -#endif - -#ifdef ENABLE_WAYLAND -#include -#endif - -class SurfaceEventFilter : public QObject { - OBSQTDisplay *display; - -public: - SurfaceEventFilter(OBSQTDisplay *src) : QObject(src), display(src) {} - -protected: - bool eventFilter(QObject *obj, QEvent *event) override - { - bool result = QObject::eventFilter(obj, event); - QPlatformSurfaceEvent *surfaceEvent; - - switch (event->type()) { - case QEvent::PlatformSurface: - surfaceEvent = static_cast(event); - - switch (surfaceEvent->surfaceEventType()) { - case QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed: - display->DestroyDisplay(); - break; - default: - break; - } - break; - default: - break; - } - - return result; - } -}; +#include "moc_OBSQTDisplay.cpp" static inline long long color_to_int(const QColor &color) { diff --git a/frontend/widgets/OBSQTDisplay.hpp b/frontend/widgets/OBSQTDisplay.hpp index e83505a22..4c1152ba5 100644 --- a/frontend/widgets/OBSQTDisplay.hpp +++ b/frontend/widgets/OBSQTDisplay.hpp @@ -1,8 +1,9 @@ #pragma once -#include #include +#include + #define GREY_COLOR_BACKGROUND 0xFF4C4C4C class OBSQTDisplay : public QWidget { diff --git a/frontend/widgets/StatusBarWidget.cpp b/frontend/widgets/StatusBarWidget.cpp index 5bcae6f33..c5a30c8b7 100644 --- a/frontend/widgets/StatusBarWidget.cpp +++ b/frontend/widgets/StatusBarWidget.cpp @@ -1,20 +1,6 @@ -#include -#include -#include "obs-app.hpp" -#include "window-basic-main.hpp" -#include "moc_window-basic-status-bar.cpp" -#include "window-basic-main-outputs.hpp" -#include "qt-wrappers.hpp" -#include "platform.hpp" - +#include "StatusBarWidget.hpp" #include "ui_StatusBarWidget.h" - -static constexpr int bitrateUpdateSeconds = 2; -static constexpr int congestionUpdateSeconds = 4; -static constexpr float excellentThreshold = 0.0f; -static constexpr float goodThreshold = 0.3333f; -static constexpr float mediocreThreshold = 0.6667f; -static constexpr float badThreshold = 1.0f; +#include "moc_StatusBarWidget.cpp" StatusBarWidget::StatusBarWidget(QWidget *parent) : QWidget(parent), ui(new Ui::StatusBarWidget) { @@ -22,580 +8,3 @@ StatusBarWidget::StatusBarWidget(QWidget *parent) : QWidget(parent), ui(new Ui:: } StatusBarWidget::~StatusBarWidget() {} - -OBSBasicStatusBar::OBSBasicStatusBar(QWidget *parent) - : QStatusBar(parent), - excellentPixmap(QIcon(":/res/images/network-excellent.svg").pixmap(QSize(16, 16))), - goodPixmap(QIcon(":/res/images/network-good.svg").pixmap(QSize(16, 16))), - mediocrePixmap(QIcon(":/res/images/network-mediocre.svg").pixmap(QSize(16, 16))), - badPixmap(QIcon(":/res/images/network-bad.svg").pixmap(QSize(16, 16))), - recordingActivePixmap(QIcon(":/res/images/recording-active.svg").pixmap(QSize(16, 16))), - recordingPausePixmap(QIcon(":/res/images/recording-pause.svg").pixmap(QSize(16, 16))), - streamingActivePixmap(QIcon(":/res/images/streaming-active.svg").pixmap(QSize(16, 16))) -{ - congestionArray.reserve(congestionUpdateSeconds); - - statusWidget = new StatusBarWidget(this); - statusWidget->ui->delayInfo->setText(""); - statusWidget->ui->droppedFrames->setText(QTStr("DroppedFrames").arg("0", "0.0")); - statusWidget->ui->statusIcon->setPixmap(inactivePixmap); - statusWidget->ui->streamIcon->setPixmap(streamingInactivePixmap); - statusWidget->ui->streamTime->setDisabled(true); - statusWidget->ui->recordIcon->setPixmap(recordingInactivePixmap); - statusWidget->ui->recordTime->setDisabled(true); - statusWidget->ui->delayFrame->hide(); - statusWidget->ui->issuesFrame->hide(); - statusWidget->ui->kbps->hide(); - - addPermanentWidget(statusWidget, 1); - setMinimumHeight(statusWidget->height()); - - UpdateIcons(); - connect(App(), &OBSApp::StyleChanged, this, &OBSBasicStatusBar::UpdateIcons); - - messageTimer = new QTimer(this); - messageTimer->setSingleShot(true); - connect(messageTimer, &QTimer::timeout, this, &OBSBasicStatusBar::clearMessage); - - clearMessage(); -} - -void OBSBasicStatusBar::Activate() -{ - if (!active) { - refreshTimer = new QTimer(this); - connect(refreshTimer, &QTimer::timeout, this, &OBSBasicStatusBar::UpdateStatusBar); - - int skipped = video_output_get_skipped_frames(obs_get_video()); - int total = video_output_get_total_frames(obs_get_video()); - - totalStreamSeconds = 0; - totalRecordSeconds = 0; - lastSkippedFrameCount = 0; - startSkippedFrameCount = skipped; - startTotalFrameCount = total; - - refreshTimer->start(1000); - active = true; - - if (streamOutput) { - statusWidget->ui->statusIcon->setPixmap(inactivePixmap); - } - } - - if (streamOutput) { - statusWidget->ui->streamIcon->setPixmap(streamingActivePixmap); - statusWidget->ui->streamTime->setDisabled(false); - statusWidget->ui->issuesFrame->show(); - statusWidget->ui->kbps->show(); - firstCongestionUpdate = true; - } - - if (recordOutput) { - statusWidget->ui->recordIcon->setPixmap(recordingActivePixmap); - statusWidget->ui->recordTime->setDisabled(false); - } -} - -void OBSBasicStatusBar::Deactivate() -{ - OBSBasic *main = qobject_cast(parent()); - if (!main) - return; - - if (!streamOutput) { - statusWidget->ui->streamTime->setText(QString("00:00:00")); - statusWidget->ui->streamTime->setDisabled(true); - statusWidget->ui->streamIcon->setPixmap(streamingInactivePixmap); - statusWidget->ui->statusIcon->setPixmap(inactivePixmap); - statusWidget->ui->delayFrame->hide(); - statusWidget->ui->issuesFrame->hide(); - statusWidget->ui->kbps->hide(); - totalStreamSeconds = 0; - congestionArray.clear(); - disconnected = false; - firstCongestionUpdate = false; - } - - if (!recordOutput) { - statusWidget->ui->recordTime->setText(QString("00:00:00")); - statusWidget->ui->recordTime->setDisabled(true); - statusWidget->ui->recordIcon->setPixmap(recordingInactivePixmap); - totalRecordSeconds = 0; - } - - if (main->outputHandler && !main->outputHandler->Active()) { - delete refreshTimer; - - statusWidget->ui->delayInfo->setText(""); - statusWidget->ui->droppedFrames->setText(QTStr("DroppedFrames").arg("0", "0.0")); - statusWidget->ui->kbps->setText("0 kbps"); - - delaySecTotal = 0; - delaySecStarting = 0; - delaySecStopping = 0; - reconnectTimeout = 0; - active = false; - overloadedNotify = true; - - statusWidget->ui->statusIcon->setPixmap(inactivePixmap); - } -} - -void OBSBasicStatusBar::UpdateDelayMsg() -{ - QString msg; - - if (delaySecTotal) { - if (delaySecStarting && !delaySecStopping) { - msg = QTStr("Basic.StatusBar.DelayStartingIn"); - msg = msg.arg(QString::number(delaySecStarting)); - - } else if (!delaySecStarting && delaySecStopping) { - msg = QTStr("Basic.StatusBar.DelayStoppingIn"); - msg = msg.arg(QString::number(delaySecStopping)); - - } else if (delaySecStarting && delaySecStopping) { - msg = QTStr("Basic.StatusBar.DelayStartingStoppingIn"); - msg = msg.arg(QString::number(delaySecStopping), QString::number(delaySecStarting)); - } else { - msg = QTStr("Basic.StatusBar.Delay"); - msg = msg.arg(QString::number(delaySecTotal)); - } - - if (!statusWidget->ui->delayFrame->isVisible()) - statusWidget->ui->delayFrame->show(); - - statusWidget->ui->delayInfo->setText(msg); - } -} - -void OBSBasicStatusBar::UpdateBandwidth() -{ - if (!streamOutput) - return; - - if (++seconds < bitrateUpdateSeconds) - return; - - OBSOutput output = OBSGetStrongRef(streamOutput); - if (!output) - return; - - uint64_t bytesSent = obs_output_get_total_bytes(output); - uint64_t bytesSentTime = os_gettime_ns(); - - if (bytesSent < lastBytesSent) - bytesSent = 0; - if (bytesSent == 0) - lastBytesSent = 0; - - uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8; - - double timePassed = double(bytesSentTime - lastBytesSentTime) / 1000000000.0; - - double kbitsPerSec = double(bitsBetween) / timePassed / 1000.0; - - QString text; - text += QString::number(kbitsPerSec, 'f', 0) + QString(" kbps"); - - statusWidget->ui->kbps->setText(text); - statusWidget->ui->kbps->setMinimumWidth(statusWidget->ui->kbps->width()); - - if (!statusWidget->ui->kbps->isVisible()) - statusWidget->ui->kbps->show(); - - lastBytesSent = bytesSent; - lastBytesSentTime = bytesSentTime; - seconds = 0; -} - -void OBSBasicStatusBar::UpdateCPUUsage() -{ - OBSBasic *main = qobject_cast(parent()); - if (!main) - return; - - QString text; - text += QString("CPU: ") + QString::number(main->GetCPUUsage(), 'f', 1) + QString("%"); - - statusWidget->ui->cpuUsage->setText(text); - statusWidget->ui->cpuUsage->setMinimumWidth(statusWidget->ui->cpuUsage->width()); - - UpdateCurrentFPS(); -} - -void OBSBasicStatusBar::UpdateCurrentFPS() -{ - struct obs_video_info ovi; - obs_get_video_info(&ovi); - float targetFPS = (float)ovi.fps_num / (float)ovi.fps_den; - - QString text = QString::asprintf("%.2f / %.2f FPS", obs_get_active_fps(), targetFPS); - - statusWidget->ui->fpsCurrent->setText(text); - statusWidget->ui->fpsCurrent->setMinimumWidth(statusWidget->ui->fpsCurrent->width()); -} - -void OBSBasicStatusBar::UpdateStreamTime() -{ - totalStreamSeconds++; - - int seconds = totalStreamSeconds % 60; - int totalMinutes = totalStreamSeconds / 60; - int minutes = totalMinutes % 60; - int hours = totalMinutes / 60; - - QString text = QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds); - statusWidget->ui->streamTime->setText(text); - if (streamOutput && !statusWidget->ui->streamTime->isEnabled()) - statusWidget->ui->streamTime->setDisabled(false); - - if (reconnectTimeout > 0) { - QString msg = QTStr("Basic.StatusBar.Reconnecting") - .arg(QString::number(retries), QString::number(reconnectTimeout)); - showMessage(msg); - disconnected = true; - statusWidget->ui->statusIcon->setPixmap(disconnectedPixmap); - congestionArray.clear(); - reconnectTimeout--; - - } else if (retries > 0) { - QString msg = QTStr("Basic.StatusBar.AttemptingReconnect"); - showMessage(msg.arg(QString::number(retries))); - } - - if (delaySecStopping > 0 || delaySecStarting > 0) { - if (delaySecStopping > 0) - --delaySecStopping; - if (delaySecStarting > 0) - --delaySecStarting; - UpdateDelayMsg(); - } -} - -extern volatile bool recording_paused; - -void OBSBasicStatusBar::UpdateRecordTime() -{ - bool paused = os_atomic_load_bool(&recording_paused); - - if (!paused) { - totalRecordSeconds++; - - if (recordOutput && !statusWidget->ui->recordTime->isEnabled()) - statusWidget->ui->recordTime->setDisabled(false); - } else { - statusWidget->ui->recordIcon->setPixmap(streamPauseIconToggle ? recordingPauseInactivePixmap - : recordingPausePixmap); - - streamPauseIconToggle = !streamPauseIconToggle; - } - - UpdateRecordTimeLabel(); -} - -void OBSBasicStatusBar::UpdateRecordTimeLabel() -{ - int seconds = totalRecordSeconds % 60; - int totalMinutes = totalRecordSeconds / 60; - int minutes = totalMinutes % 60; - int hours = totalMinutes / 60; - - QString text = QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds); - if (os_atomic_load_bool(&recording_paused)) { - text += QStringLiteral(" (PAUSED)"); - } - - statusWidget->ui->recordTime->setText(text); -} - -void OBSBasicStatusBar::UpdateDroppedFrames() -{ - if (!streamOutput) - return; - - OBSOutput output = OBSGetStrongRef(streamOutput); - if (!output) - return; - - int totalDropped = obs_output_get_frames_dropped(output); - int totalFrames = obs_output_get_total_frames(output); - double percent = (double)totalDropped / (double)totalFrames * 100.0; - - if (!totalFrames) - return; - - QString text = QTStr("DroppedFrames"); - text = text.arg(QString::number(totalDropped), QString::number(percent, 'f', 1)); - statusWidget->ui->droppedFrames->setText(text); - - if (!statusWidget->ui->issuesFrame->isVisible()) - statusWidget->ui->issuesFrame->show(); - - /* ----------------------------------- * - * calculate congestion color */ - - float congestion = obs_output_get_congestion(output); - float avgCongestion = (congestion + lastCongestion) * 0.5f; - if (avgCongestion < congestion) - avgCongestion = congestion; - if (avgCongestion > 1.0f) - avgCongestion = 1.0f; - - lastCongestion = congestion; - - if (disconnected) - return; - - bool update = firstCongestionUpdate; - float congestionOverTime = avgCongestion; - - if (congestionArray.size() >= congestionUpdateSeconds) { - congestionOverTime = accumulate(congestionArray.begin(), congestionArray.end(), 0.0f) / - (float)congestionArray.size(); - congestionArray.clear(); - update = true; - } else { - congestionArray.emplace_back(avgCongestion); - } - - if (update) { - if (congestionOverTime <= excellentThreshold + EPSILON) - statusWidget->ui->statusIcon->setPixmap(excellentPixmap); - else if (congestionOverTime <= goodThreshold) - statusWidget->ui->statusIcon->setPixmap(goodPixmap); - else if (congestionOverTime <= mediocreThreshold) - statusWidget->ui->statusIcon->setPixmap(mediocrePixmap); - else if (congestionOverTime <= badThreshold) - statusWidget->ui->statusIcon->setPixmap(badPixmap); - - firstCongestionUpdate = false; - } -} - -void OBSBasicStatusBar::OBSOutputReconnect(void *data, calldata_t *params) -{ - OBSBasicStatusBar *statusBar = reinterpret_cast(data); - - int seconds = (int)calldata_int(params, "timeout_sec"); - QMetaObject::invokeMethod(statusBar, "Reconnect", Q_ARG(int, seconds)); -} - -void OBSBasicStatusBar::OBSOutputReconnectSuccess(void *data, calldata_t *) -{ - OBSBasicStatusBar *statusBar = reinterpret_cast(data); - - QMetaObject::invokeMethod(statusBar, "ReconnectSuccess"); -} - -void OBSBasicStatusBar::Reconnect(int seconds) -{ - OBSBasic *main = qobject_cast(parent()); - - if (!retries) - main->SysTrayNotify(QTStr("Basic.SystemTray.Message.Reconnecting"), QSystemTrayIcon::Warning); - - reconnectTimeout = seconds; - - if (streamOutput) { - OBSOutput output = OBSGetStrongRef(streamOutput); - if (!output) - return; - - delaySecTotal = obs_output_get_active_delay(output); - UpdateDelayMsg(); - - retries++; - } -} - -void OBSBasicStatusBar::ReconnectClear() -{ - retries = 0; - reconnectTimeout = 0; - seconds = -1; - lastBytesSent = 0; - lastBytesSentTime = os_gettime_ns(); - delaySecTotal = 0; - UpdateDelayMsg(); -} - -void OBSBasicStatusBar::ReconnectSuccess() -{ - OBSBasic *main = qobject_cast(parent()); - - QString msg = QTStr("Basic.StatusBar.ReconnectSuccessful"); - showMessage(msg, 4000); - main->SysTrayNotify(msg, QSystemTrayIcon::Information); - ReconnectClear(); - - if (streamOutput) { - OBSOutput output = OBSGetStrongRef(streamOutput); - if (!output) - return; - - delaySecTotal = obs_output_get_active_delay(output); - UpdateDelayMsg(); - disconnected = false; - firstCongestionUpdate = true; - } -} - -void OBSBasicStatusBar::UpdateStatusBar() -{ - OBSBasic *main = qobject_cast(parent()); - - UpdateBandwidth(); - - if (streamOutput) - UpdateStreamTime(); - - if (recordOutput) - UpdateRecordTime(); - - UpdateDroppedFrames(); - - int skipped = video_output_get_skipped_frames(obs_get_video()); - int total = video_output_get_total_frames(obs_get_video()); - - skipped -= startSkippedFrameCount; - total -= startTotalFrameCount; - - int diff = skipped - lastSkippedFrameCount; - double percentage = double(skipped) / double(total) * 100.0; - - if (diff > 10 && percentage >= 0.1f) { - showMessage(QTStr("HighResourceUsage"), 4000); - if (!main->isVisible() && overloadedNotify) { - main->SysTrayNotify(QTStr("HighResourceUsage"), QSystemTrayIcon::Warning); - overloadedNotify = false; - } - } - - lastSkippedFrameCount = skipped; -} - -void OBSBasicStatusBar::StreamDelayStarting(int sec) -{ - OBSBasic *main = qobject_cast(parent()); - if (!main || !main->outputHandler) - return; - - OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); - streamOutput = OBSGetWeakRef(output); - - delaySecTotal = delaySecStarting = sec; - UpdateDelayMsg(); - Activate(); -} - -void OBSBasicStatusBar::StreamDelayStopping(int sec) -{ - delaySecTotal = delaySecStopping = sec; - UpdateDelayMsg(); -} - -void OBSBasicStatusBar::StreamStarted(obs_output_t *output) -{ - streamOutput = OBSGetWeakRef(output); - - streamSigs.emplace_back(obs_output_get_signal_handler(output), "reconnect", OBSOutputReconnect, this); - streamSigs.emplace_back(obs_output_get_signal_handler(output), "reconnect_success", OBSOutputReconnectSuccess, - this); - - retries = 0; - lastBytesSent = 0; - lastBytesSentTime = os_gettime_ns(); - Activate(); -} - -void OBSBasicStatusBar::StreamStopped() -{ - if (streamOutput) { - streamSigs.clear(); - - ReconnectClear(); - streamOutput = nullptr; - clearMessage(); - Deactivate(); - } -} - -void OBSBasicStatusBar::RecordingStarted(obs_output_t *output) -{ - recordOutput = OBSGetWeakRef(output); - Activate(); -} - -void OBSBasicStatusBar::RecordingStopped() -{ - recordOutput = nullptr; - Deactivate(); -} - -void OBSBasicStatusBar::RecordingPaused() -{ - if (recordOutput) { - statusWidget->ui->recordIcon->setPixmap(recordingPausePixmap); - streamPauseIconToggle = true; - } - - UpdateRecordTimeLabel(); -} - -void OBSBasicStatusBar::RecordingUnpaused() -{ - if (recordOutput) { - statusWidget->ui->recordIcon->setPixmap(recordingActivePixmap); - } - - UpdateRecordTimeLabel(); -} - -static QPixmap GetPixmap(const QString &filename) -{ - QString path = obs_frontend_is_theme_dark() ? "theme:Dark/" : ":/res/images/"; - return QIcon(path + filename).pixmap(QSize(16, 16)); -} - -void OBSBasicStatusBar::UpdateIcons() -{ - disconnectedPixmap = GetPixmap("network-disconnected.svg"); - inactivePixmap = GetPixmap("network-inactive.svg"); - - streamingInactivePixmap = GetPixmap("streaming-inactive.svg"); - - recordingInactivePixmap = GetPixmap("recording-inactive.svg"); - recordingPauseInactivePixmap = GetPixmap("recording-pause-inactive.svg"); - - bool streaming = obs_frontend_streaming_active(); - - if (!streaming) { - statusWidget->ui->streamIcon->setPixmap(streamingInactivePixmap); - statusWidget->ui->statusIcon->setPixmap(inactivePixmap); - } else { - if (disconnected) - statusWidget->ui->statusIcon->setPixmap(disconnectedPixmap); - } - - bool recording = obs_frontend_recording_active(); - - if (!recording) - statusWidget->ui->recordIcon->setPixmap(recordingInactivePixmap); -} - -void OBSBasicStatusBar::showMessage(const QString &message, int timeout) -{ - messageTimer->stop(); - - statusWidget->ui->message->setText(message); - - if (timeout) - messageTimer->start(timeout); -} - -void OBSBasicStatusBar::clearMessage() -{ - statusWidget->ui->message->setText(""); -} diff --git a/frontend/widgets/StatusBarWidget.hpp b/frontend/widgets/StatusBarWidget.hpp index 38be9e520..67e803716 100644 --- a/frontend/widgets/StatusBarWidget.hpp +++ b/frontend/widgets/StatusBarWidget.hpp @@ -1,11 +1,8 @@ #pragma once -#include -#include -#include -#include -#include +#include +class OBSBasicStatusBar; class Ui_StatusBarWidget; class StatusBarWidget : public QWidget { @@ -20,100 +17,3 @@ public: StatusBarWidget(QWidget *parent = nullptr); ~StatusBarWidget(); }; - -class OBSBasicStatusBar : public QStatusBar { - Q_OBJECT - -private: - StatusBarWidget *statusWidget = nullptr; - - OBSWeakOutputAutoRelease streamOutput; - std::vector streamSigs; - OBSWeakOutputAutoRelease recordOutput; - bool active = false; - bool overloadedNotify = true; - bool streamPauseIconToggle = false; - bool disconnected = false; - bool firstCongestionUpdate = false; - - std::vector congestionArray; - - int retries = 0; - int totalStreamSeconds = 0; - int totalRecordSeconds = 0; - - int reconnectTimeout = 0; - - int delaySecTotal = 0; - int delaySecStarting = 0; - int delaySecStopping = 0; - - int startSkippedFrameCount = 0; - int startTotalFrameCount = 0; - int lastSkippedFrameCount = 0; - - int seconds = 0; - uint64_t lastBytesSent = 0; - uint64_t lastBytesSentTime = 0; - - QPixmap excellentPixmap; - QPixmap goodPixmap; - QPixmap mediocrePixmap; - QPixmap badPixmap; - QPixmap disconnectedPixmap; - QPixmap inactivePixmap; - - QPixmap recordingActivePixmap; - QPixmap recordingPausePixmap; - QPixmap recordingPauseInactivePixmap; - QPixmap recordingInactivePixmap; - QPixmap streamingActivePixmap; - QPixmap streamingInactivePixmap; - - float lastCongestion = 0.0f; - - QPointer refreshTimer; - QPointer messageTimer; - - obs_output_t *GetOutput(); - - void Activate(); - void Deactivate(); - - void UpdateDelayMsg(); - void UpdateBandwidth(); - void UpdateStreamTime(); - void UpdateRecordTime(); - void UpdateRecordTimeLabel(); - void UpdateDroppedFrames(); - - static void OBSOutputReconnect(void *data, calldata_t *params); - static void OBSOutputReconnectSuccess(void *data, calldata_t *params); - -public slots: - void UpdateCPUUsage(); - - void clearMessage(); - void showMessage(const QString &message, int timeout = 0); - -private slots: - void Reconnect(int seconds); - void ReconnectSuccess(); - void UpdateStatusBar(); - void UpdateCurrentFPS(); - void UpdateIcons(); - -public: - OBSBasicStatusBar(QWidget *parent); - - void StreamDelayStarting(int sec); - void StreamDelayStopping(int sec); - void StreamStarted(obs_output_t *output); - void StreamStopped(); - void RecordingStarted(obs_output_t *output); - void RecordingStopped(); - void RecordingPaused(); - void RecordingUnpaused(); - - void ReconnectClear(); -}; diff --git a/frontend/widgets/VolControl.cpp b/frontend/widgets/VolControl.cpp index cd00c14d7..ea9ee5c6b 100644 --- a/frontend/widgets/VolControl.cpp +++ b/frontend/widgets/VolControl.cpp @@ -1,29 +1,14 @@ -#include "window-basic-main.hpp" -#include "moc_volume-control.cpp" -#include "obs-app.hpp" -#include "mute-checkbox.hpp" -#include "absolute-slider.hpp" -#include "source-label.hpp" +#include "VolControl.hpp" +#include "VolumeMeter.hpp" +#include "OBSBasic.hpp" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include -using namespace std; +#include -#define FADER_PRECISION 4096.0 - -// Size of the audio indicator in pixels -#define INDICATOR_THICKNESS 3 - -// Padding on top and bottom of vertical meters -#define METER_PADDING 1 - -std::weak_ptr VolumeMeter::updateTimer; +#include "moc_VolControl.cpp" static inline Qt::CheckState GetCheckState(bool muted, bool unassigned) { @@ -404,238 +389,6 @@ VolControl::~VolControl() contextMenu->close(); } -static inline QColor color_from_int(long long val) -{ - QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); - color.setAlpha(255); - - return color; -} - -QColor VolumeMeter::getBackgroundNominalColor() const -{ - return p_backgroundNominalColor; -} - -QColor VolumeMeter::getBackgroundNominalColorDisabled() const -{ - return backgroundNominalColorDisabled; -} - -void VolumeMeter::setBackgroundNominalColor(QColor c) -{ - p_backgroundNominalColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundNominalColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreen")); - } else { - backgroundNominalColor = p_backgroundNominalColor; - } -} - -void VolumeMeter::setBackgroundNominalColorDisabled(QColor c) -{ - backgroundNominalColorDisabled = std::move(c); -} - -QColor VolumeMeter::getBackgroundWarningColor() const -{ - return p_backgroundWarningColor; -} - -QColor VolumeMeter::getBackgroundWarningColorDisabled() const -{ - return backgroundWarningColorDisabled; -} - -void VolumeMeter::setBackgroundWarningColor(QColor c) -{ - p_backgroundWarningColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundWarningColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellow")); - } else { - backgroundWarningColor = p_backgroundWarningColor; - } -} - -void VolumeMeter::setBackgroundWarningColorDisabled(QColor c) -{ - backgroundWarningColorDisabled = std::move(c); -} - -QColor VolumeMeter::getBackgroundErrorColor() const -{ - return p_backgroundErrorColor; -} - -QColor VolumeMeter::getBackgroundErrorColorDisabled() const -{ - return backgroundErrorColorDisabled; -} - -void VolumeMeter::setBackgroundErrorColor(QColor c) -{ - p_backgroundErrorColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundErrorColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerRed")); - } else { - backgroundErrorColor = p_backgroundErrorColor; - } -} - -void VolumeMeter::setBackgroundErrorColorDisabled(QColor c) -{ - backgroundErrorColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundNominalColor() const -{ - return p_foregroundNominalColor; -} - -QColor VolumeMeter::getForegroundNominalColorDisabled() const -{ - return foregroundNominalColorDisabled; -} - -void VolumeMeter::setForegroundNominalColor(QColor c) -{ - p_foregroundNominalColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundNominalColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreenActive")); - } else { - foregroundNominalColor = p_foregroundNominalColor; - } -} - -void VolumeMeter::setForegroundNominalColorDisabled(QColor c) -{ - foregroundNominalColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundWarningColor() const -{ - return p_foregroundWarningColor; -} - -QColor VolumeMeter::getForegroundWarningColorDisabled() const -{ - return foregroundWarningColorDisabled; -} - -void VolumeMeter::setForegroundWarningColor(QColor c) -{ - p_foregroundWarningColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundWarningColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellowActive")); - } else { - foregroundWarningColor = p_foregroundWarningColor; - } -} - -void VolumeMeter::setForegroundWarningColorDisabled(QColor c) -{ - foregroundWarningColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundErrorColor() const -{ - return p_foregroundErrorColor; -} - -QColor VolumeMeter::getForegroundErrorColorDisabled() const -{ - return foregroundErrorColorDisabled; -} - -void VolumeMeter::setForegroundErrorColor(QColor c) -{ - p_foregroundErrorColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundErrorColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerRedActive")); - } else { - foregroundErrorColor = p_foregroundErrorColor; - } -} - -void VolumeMeter::setForegroundErrorColorDisabled(QColor c) -{ - foregroundErrorColorDisabled = std::move(c); -} - -QColor VolumeMeter::getClipColor() const -{ - return clipColor; -} - -void VolumeMeter::setClipColor(QColor c) -{ - clipColor = std::move(c); -} - -QColor VolumeMeter::getMagnitudeColor() const -{ - return magnitudeColor; -} - -void VolumeMeter::setMagnitudeColor(QColor c) -{ - magnitudeColor = std::move(c); -} - -QColor VolumeMeter::getMajorTickColor() const -{ - return majorTickColor; -} - -void VolumeMeter::setMajorTickColor(QColor c) -{ - majorTickColor = std::move(c); -} - -QColor VolumeMeter::getMinorTickColor() const -{ - return minorTickColor; -} - -void VolumeMeter::setMinorTickColor(QColor c) -{ - minorTickColor = std::move(c); -} - -int VolumeMeter::getMeterThickness() const -{ - return meterThickness; -} - -void VolumeMeter::setMeterThickness(int v) -{ - meterThickness = v; - recalculateLayout = true; -} - -qreal VolumeMeter::getMeterFontScaling() const -{ - return meterFontScaling; -} - -void VolumeMeter::setMeterFontScaling(qreal v) -{ - meterFontScaling = v; - recalculateLayout = true; -} - void VolControl::refreshColors() { volMeter->setBackgroundNominalColor(volMeter->getBackgroundNominalColor()); @@ -645,863 +398,3 @@ void VolControl::refreshColors() volMeter->setForegroundWarningColor(volMeter->getForegroundWarningColor()); volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor()); } - -qreal VolumeMeter::getMinimumLevel() const -{ - return minimumLevel; -} - -void VolumeMeter::setMinimumLevel(qreal v) -{ - minimumLevel = v; -} - -qreal VolumeMeter::getWarningLevel() const -{ - return warningLevel; -} - -void VolumeMeter::setWarningLevel(qreal v) -{ - warningLevel = v; -} - -qreal VolumeMeter::getErrorLevel() const -{ - return errorLevel; -} - -void VolumeMeter::setErrorLevel(qreal v) -{ - errorLevel = v; -} - -qreal VolumeMeter::getClipLevel() const -{ - return clipLevel; -} - -void VolumeMeter::setClipLevel(qreal v) -{ - clipLevel = v; -} - -qreal VolumeMeter::getMinimumInputLevel() const -{ - return minimumInputLevel; -} - -void VolumeMeter::setMinimumInputLevel(qreal v) -{ - minimumInputLevel = v; -} - -qreal VolumeMeter::getPeakDecayRate() const -{ - return peakDecayRate; -} - -void VolumeMeter::setPeakDecayRate(qreal v) -{ - peakDecayRate = v; -} - -qreal VolumeMeter::getMagnitudeIntegrationTime() const -{ - return magnitudeIntegrationTime; -} - -void VolumeMeter::setMagnitudeIntegrationTime(qreal v) -{ - magnitudeIntegrationTime = v; -} - -qreal VolumeMeter::getPeakHoldDuration() const -{ - return peakHoldDuration; -} - -void VolumeMeter::setPeakHoldDuration(qreal v) -{ - peakHoldDuration = v; -} - -qreal VolumeMeter::getInputPeakHoldDuration() const -{ - return inputPeakHoldDuration; -} - -void VolumeMeter::setInputPeakHoldDuration(qreal v) -{ - inputPeakHoldDuration = v; -} - -void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType) -{ - obs_volmeter_set_peak_meter_type(obs_volmeter, peakMeterType); - switch (peakMeterType) { - case TRUE_PEAK_METER: - // For true-peak meters EBU has defined the Permitted Maximum, - // taking into account the accuracy of the meter and further - // processing required by lossy audio compression. - // - // The alignment level was not specified, but I've adjusted - // it compared to a sample-peak meter. Incidentally Youtube - // uses this new Alignment Level as the maximum integrated - // loudness of a video. - // - // * Permitted Maximum Level (PML) = -2.0 dBTP - // * Alignment Level (AL) = -13 dBTP - setErrorLevel(-2.0); - setWarningLevel(-13.0); - break; - - case SAMPLE_PEAK_METER: - default: - // For a sample Peak Meter EBU has the following level - // definitions, taking into account inaccuracies of this meter: - // - // * Permitted Maximum Level (PML) = -9.0 dBFS - // * Alignment Level (AL) = -20.0 dBFS - setErrorLevel(-9.0); - setWarningLevel(-20.0); - break; - } -} - -void VolumeMeter::mousePressEvent(QMouseEvent *event) -{ - setFocus(Qt::MouseFocusReason); - event->accept(); -} - -void VolumeMeter::wheelEvent(QWheelEvent *event) -{ - QApplication::sendEvent(focusProxy(), event); -} - -VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter, bool vertical) - : QWidget(parent), - obs_volmeter(obs_volmeter), - vertical(vertical) -{ - setAttribute(Qt::WA_OpaquePaintEvent, true); - - // Default meter settings, they only show if - // there is no stylesheet, do not remove. - backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green - backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow - backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red - foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green - foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow - foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red - - backgroundNominalColorDisabled.setRgb(90, 90, 90); - backgroundWarningColorDisabled.setRgb(117, 117, 117); - backgroundErrorColorDisabled.setRgb(65, 65, 65); - foregroundNominalColorDisabled.setRgb(163, 163, 163); - foregroundWarningColorDisabled.setRgb(217, 217, 217); - foregroundErrorColorDisabled.setRgb(113, 113, 113); - - clipColor.setRgb(0xff, 0xff, 0xff); // Bright white - magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black - majorTickColor.setRgb(0x00, 0x00, 0x00); // Black - minorTickColor.setRgb(0x32, 0x32, 0x32); // Dark gray - minimumLevel = -60.0; // -60 dB - warningLevel = -20.0; // -20 dB - errorLevel = -9.0; // -9 dB - clipLevel = -0.5; // -0.5 dB - minimumInputLevel = -50.0; // -50 dB - peakDecayRate = 11.76; // 20 dB / 1.7 sec - magnitudeIntegrationTime = 0.3; // 99% in 300 ms - peakHoldDuration = 20.0; // 20 seconds - inputPeakHoldDuration = 1.0; // 1 second - meterThickness = 3; // Bar thickness in pixels - meterFontScaling = 0.7; // Font size for numbers is 70% of Widget's font size - channels = (int)audio_output_get_channels(obs_get_audio()); - - doLayout(); - updateTimerRef = updateTimer.lock(); - if (!updateTimerRef) { - updateTimerRef = std::make_shared(); - updateTimerRef->setTimerType(Qt::PreciseTimer); - updateTimerRef->start(16); - updateTimer = updateTimerRef; - } - - updateTimerRef->AddVolControl(this); -} - -VolumeMeter::~VolumeMeter() -{ - updateTimerRef->RemoveVolControl(this); -} - -void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], - const float inputPeak[MAX_AUDIO_CHANNELS]) -{ - uint64_t ts = os_gettime_ns(); - QMutexLocker locker(&dataMutex); - - currentLastUpdateTime = ts; - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { - currentMagnitude[channelNr] = magnitude[channelNr]; - currentPeak[channelNr] = peak[channelNr]; - currentInputPeak[channelNr] = inputPeak[channelNr]; - } - - // In case there are more updates then redraws we must make sure - // that the ballistics of peak and hold are recalculated. - locker.unlock(); - calculateBallistics(ts); -} - -inline void VolumeMeter::resetLevels() -{ - currentLastUpdateTime = 0; - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { - currentMagnitude[channelNr] = -M_INFINITE; - currentPeak[channelNr] = -M_INFINITE; - currentInputPeak[channelNr] = -M_INFINITE; - - displayMagnitude[channelNr] = -M_INFINITE; - displayPeak[channelNr] = -M_INFINITE; - displayPeakHold[channelNr] = -M_INFINITE; - displayPeakHoldLastUpdateTime[channelNr] = 0; - displayInputPeakHold[channelNr] = -M_INFINITE; - displayInputPeakHoldLastUpdateTime[channelNr] = 0; - } -} - -bool VolumeMeter::needLayoutChange() -{ - int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); - - if (!currentNrAudioChannels) { - struct obs_audio_info oai; - obs_get_audio_info(&oai); - currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1 : 2; - } - - if (displayNrAudioChannels != currentNrAudioChannels) { - displayNrAudioChannels = currentNrAudioChannels; - recalculateLayout = true; - } - - return recalculateLayout; -} - -// When this is called from the constructor, obs_volmeter_get_nr_channels has not -// yet been called and Q_PROPERTY settings have not yet been read from the -// stylesheet. -inline void VolumeMeter::doLayout() -{ - QMutexLocker locker(&dataMutex); - - if (displayNrAudioChannels) { - int meterSize = std::floor(22 / displayNrAudioChannels); - setMeterThickness(std::clamp(meterSize, 3, 7)); - } - recalculateLayout = false; - - tickFont = font(); - QFontInfo info(tickFont); - tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling); - QFontMetrics metrics(tickFont); - if (vertical) { - // Each meter channel is meterThickness pixels wide, plus one pixel - // between channels, but not after the last. - // Add 4 pixels for ticks, space to hold our longest label in this font, - // and a few pixels before the fader. - QRect scaleBounds = metrics.boundingRect("-88"); - setMinimumSize(displayNrAudioChannels * (meterThickness + 1) - 1 + 10 + scaleBounds.width() + 2, 100); - } else { - // Each meter channel is meterThickness pixels high, plus one pixel - // between channels, but not after the last. - // Add 4 pixels for ticks, and space high enough to hold our label in - // this font, presuming that digits don't have descenders. - setMinimumSize(100, displayNrAudioChannels * (meterThickness + 1) - 1 + 4 + metrics.capHeight()); - } - - resetLevels(); -} - -inline bool VolumeMeter::detectIdle(uint64_t ts) -{ - double timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001; - if (timeSinceLastUpdate > 0.5) { - resetLevels(); - return true; - } else { - return false; - } -} - -inline void VolumeMeter::calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw) -{ - if (currentPeak[channelNr] >= displayPeak[channelNr] || isnan(displayPeak[channelNr])) { - // Attack of peak is immediate. - displayPeak[channelNr] = currentPeak[channelNr]; - } else { - // Decay of peak is 40 dB / 1.7 seconds for Fast Profile - // 20 dB / 1.7 seconds for Medium Profile (Type I PPM) - // 24 dB / 2.8 seconds for Slow Profile (Type II PPM) - float decay = float(peakDecayRate * timeSinceLastRedraw); - displayPeak[channelNr] = - std::clamp(displayPeak[channelNr] - decay, std::min(currentPeak[channelNr], 0.f), 0.f); - } - - if (currentPeak[channelNr] >= displayPeakHold[channelNr] || !isfinite(displayPeakHold[channelNr])) { - // Attack of peak-hold is immediate, but keep track - // when it was last updated. - displayPeakHold[channelNr] = currentPeak[channelNr]; - displayPeakHoldLastUpdateTime[channelNr] = ts; - } else { - // The peak and hold falls back to peak - // after 20 seconds. - qreal timeSinceLastPeak = (uint64_t)(ts - displayPeakHoldLastUpdateTime[channelNr]) * 0.000000001; - if (timeSinceLastPeak > peakHoldDuration) { - displayPeakHold[channelNr] = currentPeak[channelNr]; - displayPeakHoldLastUpdateTime[channelNr] = ts; - } - } - - if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] || - !isfinite(displayInputPeakHold[channelNr])) { - // Attack of peak-hold is immediate, but keep track - // when it was last updated. - displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; - displayInputPeakHoldLastUpdateTime[channelNr] = ts; - } else { - // The peak and hold falls back to peak after 1 second. - qreal timeSinceLastPeak = (uint64_t)(ts - displayInputPeakHoldLastUpdateTime[channelNr]) * 0.000000001; - if (timeSinceLastPeak > inputPeakHoldDuration) { - displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; - displayInputPeakHoldLastUpdateTime[channelNr] = ts; - } - } - - if (!isfinite(displayMagnitude[channelNr])) { - // The statements in the else-leg do not work with - // NaN and infinite displayMagnitude. - displayMagnitude[channelNr] = currentMagnitude[channelNr]; - } else { - // A VU meter will integrate to the new value to 99% in 300 ms. - // The calculation here is very simplified and is more accurate - // with higher frame-rate. - float attack = float((currentMagnitude[channelNr] - displayMagnitude[channelNr]) * - (timeSinceLastRedraw / magnitudeIntegrationTime) * 0.99); - displayMagnitude[channelNr] = - std::clamp(displayMagnitude[channelNr] + attack, (float)minimumLevel, 0.f); - } -} - -inline void VolumeMeter::calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw) -{ - QMutexLocker locker(&dataMutex); - - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) - calculateBallisticsForChannel(channelNr, ts, timeSinceLastRedraw); -} - -void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width, int height, float peakHold) -{ - QMutexLocker locker(&dataMutex); - QColor color; - - if (peakHold < minimumInputLevel) - color = backgroundNominalColor; - else if (peakHold < warningLevel) - color = foregroundNominalColor; - else if (peakHold < errorLevel) - color = foregroundWarningColor; - else if (peakHold <= clipLevel) - color = foregroundErrorColor; - else - color = clipColor; - - painter.fillRect(x, y, width, height, color); -} - -void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width) -{ - qreal scale = width / minimumLevel; - - painter.setFont(tickFont); - QFontMetrics metrics(tickFont); - painter.setPen(majorTickColor); - - // Draw major tick lines and numeric indicators. - for (int i = 0; i >= minimumLevel; i -= 5) { - int position = int(x + width - (i * scale) - 1); - QString str = QString::number(i); - - // Center the number on the tick, but don't overflow - QRect textBounds = metrics.boundingRect(str); - int pos; - if (i == 0) { - pos = position - textBounds.width(); - } else { - pos = position - (textBounds.width() / 2); - if (pos < 0) - pos = 0; - } - painter.drawText(pos, y + 4 + metrics.capHeight(), str); - - painter.drawLine(position, y, position, y + 2); - } -} - -void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height) -{ - qreal scale = height / minimumLevel; - - painter.setFont(tickFont); - QFontMetrics metrics(tickFont); - painter.setPen(majorTickColor); - - // Draw major tick lines and numeric indicators. - for (int i = 0; i >= minimumLevel; i -= 5) { - int position = y + int(i * scale) + METER_PADDING; - QString str = QString::number(i); - - // Center the number on the tick, but don't overflow - if (i == 0) { - painter.drawText(x + 10, position + metrics.capHeight(), str); - } else { - painter.drawText(x + 8, position + (metrics.capHeight() / 2), str); - } - - painter.drawLine(x, position, x + 2, position); - } -} - -#define CLIP_FLASH_DURATION_MS 1000 - -inline int VolumeMeter::convertToInt(float number) -{ - constexpr int min = std::numeric_limits::min(); - constexpr int max = std::numeric_limits::max(); - - // NOTE: Conversion from 'const int' to 'float' changes max value from 2147483647 to 2147483648 - if (number >= (float)max) - return max; - else if (number < min) - return min; - else - return int(number); -} - -void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold) -{ - qreal scale = width / minimumLevel; - - QMutexLocker locker(&dataMutex); - int minimumPosition = x + 0; - int maximumPosition = x + width; - int magnitudePosition = x + width - convertToInt(magnitude * scale); - int peakPosition = x + width - convertToInt(peak * scale); - int peakHoldPosition = x + width - convertToInt(peakHold * scale); - int warningPosition = x + width - convertToInt(warningLevel * scale); - int errorPosition = x + width - convertToInt(errorLevel * scale); - - int nominalLength = warningPosition - minimumPosition; - int warningLength = errorPosition - warningPosition; - int errorLength = maximumPosition - errorPosition; - locker.unlock(); - - if (clipping) { - peakPosition = maximumPosition; - } - - if (peakPosition < minimumPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < warningPosition) { - painter.fillRect(minimumPosition, y, peakPosition - minimumPosition, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(peakPosition, y, warningPosition - peakPosition, height, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < errorPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(warningPosition, y, peakPosition - warningPosition, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(peakPosition, y, errorPosition - peakPosition, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < maximumPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(errorPosition, y, peakPosition - errorPosition, height, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - painter.fillRect(peakPosition, y, maximumPosition - peakPosition, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else { - if (!clipping) { - QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, [&]() { clipping = false; }); - clipping = true; - } - - int end = errorLength + warningLength + nominalLength; - painter.fillRect(minimumPosition, y, end, height, - QBrush(muted ? foregroundErrorColorDisabled : foregroundErrorColor)); - } - - if (peakHoldPosition - 3 < minimumPosition) - ; // Peak-hold below minimum, no drawing. - else if (peakHoldPosition < warningPosition) - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - else if (peakHoldPosition < errorPosition) - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - else - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - - if (magnitudePosition - 3 >= minimumPosition) - painter.fillRect(magnitudePosition - 3, y, 3, height, magnitudeColor); -} - -void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold) -{ - qreal scale = height / minimumLevel; - - QMutexLocker locker(&dataMutex); - int minimumPosition = y + 0; - int maximumPosition = y + height; - int magnitudePosition = y + height - convertToInt(magnitude * scale); - int peakPosition = y + height - convertToInt(peak * scale); - int peakHoldPosition = y + height - convertToInt(peakHold * scale); - int warningPosition = y + height - convertToInt(warningLevel * scale); - int errorPosition = y + height - convertToInt(errorLevel * scale); - - int nominalLength = warningPosition - minimumPosition; - int warningLength = errorPosition - warningPosition; - int errorLength = maximumPosition - errorPosition; - locker.unlock(); - - if (clipping) { - peakPosition = maximumPosition; - } - - if (peakPosition < minimumPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < warningPosition) { - painter.fillRect(x, minimumPosition, width, peakPosition - minimumPosition, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, peakPosition, width, warningPosition - peakPosition, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < errorPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, warningPosition, width, peakPosition - warningPosition, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(x, peakPosition, width, errorPosition - peakPosition, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < maximumPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(x, errorPosition, width, peakPosition - errorPosition, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - painter.fillRect(x, peakPosition, width, maximumPosition - peakPosition, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else { - if (!clipping) { - QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, [&]() { clipping = false; }); - clipping = true; - } - - int end = errorLength + warningLength + nominalLength; - painter.fillRect(x, minimumPosition, width, end, - QBrush(muted ? foregroundErrorColorDisabled : foregroundErrorColor)); - } - - if (peakHoldPosition - 3 < minimumPosition) - ; // Peak-hold below minimum, no drawing. - else if (peakHoldPosition < warningPosition) - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - else if (peakHoldPosition < errorPosition) - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - else - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - - if (magnitudePosition - 3 >= minimumPosition) - painter.fillRect(x, magnitudePosition - 3, width, 3, magnitudeColor); -} - -void VolumeMeter::paintEvent(QPaintEvent *event) -{ - uint64_t ts = os_gettime_ns(); - qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; - calculateBallistics(ts, timeSinceLastRedraw); - bool idle = detectIdle(ts); - - QRect widgetRect = rect(); - int width = widgetRect.width(); - int height = widgetRect.height(); - - QPainter painter(this); - - // Paint window background color (as widget is opaque) - QColor background = palette().color(QPalette::ColorRole::Window); - painter.fillRect(event->region().boundingRect(), background); - - if (vertical) - height -= METER_PADDING * 2; - - // timerEvent requests update of the bar(s) only, so we can avoid the - // overhead of repainting the scale and labels. - if (event->region().boundingRect() != getBarRect()) { - if (needLayoutChange()) - doLayout(); - - if (vertical) { - paintVTicks(painter, displayNrAudioChannels * (meterThickness + 1) - 1, 0, - height - (INDICATOR_THICKNESS + 3)); - } else { - paintHTicks(painter, INDICATOR_THICKNESS + 3, displayNrAudioChannels * (meterThickness + 1) - 1, - width - (INDICATOR_THICKNESS + 3)); - } - } - - if (vertical) { - // Invert the Y axis to ease the math - painter.translate(0, height + METER_PADDING); - painter.scale(1, -1); - } - - for (int channelNr = 0; channelNr < displayNrAudioChannels; channelNr++) { - - int channelNrFixed = (displayNrAudioChannels == 1 && channels > 2) ? 2 : channelNr; - - if (vertical) - paintVMeter(painter, channelNr * (meterThickness + 1), INDICATOR_THICKNESS + 2, meterThickness, - height - (INDICATOR_THICKNESS + 2), displayMagnitude[channelNrFixed], - displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); - else - paintHMeter(painter, INDICATOR_THICKNESS + 2, channelNr * (meterThickness + 1), - width - (INDICATOR_THICKNESS + 2), meterThickness, displayMagnitude[channelNrFixed], - displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); - - if (idle) - continue; - - // By not drawing the input meter boxes the user can - // see that the audio stream has been stopped, without - // having too much visual impact. - if (vertical) - paintInputMeter(painter, channelNr * (meterThickness + 1), 0, meterThickness, - INDICATOR_THICKNESS, displayInputPeakHold[channelNrFixed]); - else - paintInputMeter(painter, 0, channelNr * (meterThickness + 1), INDICATOR_THICKNESS, - meterThickness, displayInputPeakHold[channelNrFixed]); - } - - lastRedrawTime = ts; -} - -QRect VolumeMeter::getBarRect() const -{ - QRect rec = rect(); - if (vertical) - rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1); - else - rec.setHeight(displayNrAudioChannels * (meterThickness + 1) - 1); - - return rec; -} - -void VolumeMeter::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::StyleChange) - recalculateLayout = true; - - QWidget::changeEvent(e); -} - -void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) -{ - volumeMeters.push_back(meter); -} - -void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter) -{ - volumeMeters.removeOne(meter); -} - -void VolumeMeterTimer::timerEvent(QTimerEvent *) -{ - for (VolumeMeter *meter : volumeMeters) { - if (meter->needLayoutChange()) { - // Tell paintEvent to update layout and paint everything - meter->update(); - } else { - // Tell paintEvent to paint only the bars - meter->update(meter->getBarRect()); - } - } -} - -VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent) : AbsoluteSlider(parent) -{ - fad = fader; -} - -VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, QWidget *parent) - : AbsoluteSlider(orientation, parent) -{ - fad = fader; -} - -bool VolumeSlider::getDisplayTicks() const -{ - return displayTicks; -} - -void VolumeSlider::setDisplayTicks(bool display) -{ - displayTicks = display; -} - -void VolumeSlider::paintEvent(QPaintEvent *event) -{ - if (!getDisplayTicks()) { - QSlider::paintEvent(event); - return; - } - - QPainter painter(this); - QColor tickColor(91, 98, 115, 255); - - obs_fader_conversion_t fader_db_to_def = obs_fader_db_to_def(fad); - - QStyleOptionSlider opt; - initStyleOption(&opt); - - QRect groove = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); - QRect handle = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); - - if (orientation() == Qt::Horizontal) { - const int sliderWidth = groove.width() - handle.width(); - - float tickLength = groove.height() * 1.5; - tickLength = std::max((int)tickLength + groove.height(), 8 + groove.height()); - - float yPos = groove.center().y() - (tickLength / 2) + 1; - - for (int db = -10; db >= -90; db -= 10) { - float tickValue = fader_db_to_def(db); - - float xPos = groove.left() + (tickValue * sliderWidth) + (handle.width() / 2); - painter.fillRect(xPos, yPos, 1, tickLength, tickColor); - } - } - - if (orientation() == Qt::Vertical) { - const int sliderHeight = groove.height() - handle.height(); - - float tickLength = groove.width() * 1.5; - tickLength = std::max((int)tickLength + groove.width(), 8 + groove.width()); - - float xPos = groove.center().x() - (tickLength / 2) + 1; - - for (int db = -10; db >= -96; db -= 10) { - float tickValue = fader_db_to_def(db); - - float yPos = - groove.height() + groove.top() - (tickValue * sliderHeight) - (handle.height() / 2); - painter.fillRect(xPos, yPos, tickLength, 1, tickColor); - } - } - - QSlider::paintEvent(event); -} - -VolumeAccessibleInterface::VolumeAccessibleInterface(QWidget *w) : QAccessibleWidget(w) {} - -VolumeSlider *VolumeAccessibleInterface::slider() const -{ - return qobject_cast(object()); -} - -QString VolumeAccessibleInterface::text(QAccessible::Text t) const -{ - if (slider()->isVisible()) { - switch (t) { - case QAccessible::Text::Value: - return currentValue().toString(); - default: - break; - } - } - return QAccessibleWidget::text(t); -} - -QVariant VolumeAccessibleInterface::currentValue() const -{ - QString text; - float db = obs_fader_get_db(slider()->fad); - - if (db < -96.0f) - text = "-inf dB"; - else - text = QString::number(db, 'f', 1).append(" dB"); - - return text; -} - -void VolumeAccessibleInterface::setCurrentValue(const QVariant &value) -{ - slider()->setValue(value.toInt()); -} - -QVariant VolumeAccessibleInterface::maximumValue() const -{ - return slider()->maximum(); -} - -QVariant VolumeAccessibleInterface::minimumValue() const -{ - return slider()->minimum(); -} - -QVariant VolumeAccessibleInterface::minimumStepSize() const -{ - return slider()->singleStep(); -} - -QAccessible::Role VolumeAccessibleInterface::role() const -{ - return QAccessible::Role::Slider; -} diff --git a/frontend/widgets/VolControl.hpp b/frontend/widgets/VolControl.hpp index 51c77f247..fe4ba8a20 100644 --- a/frontend/widgets/VolControl.hpp +++ b/frontend/widgets/VolControl.hpp @@ -1,249 +1,15 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include -#include "absolute-slider.hpp" -class QPushButton; -class VolumeMeterTimer; -class VolumeSlider; +#include -class VolumeMeter : public QWidget { - Q_OBJECT - Q_PROPERTY(QColor backgroundNominalColor READ getBackgroundNominalColor WRITE setBackgroundNominalColor - DESIGNABLE true) - Q_PROPERTY(QColor backgroundWarningColor READ getBackgroundWarningColor WRITE setBackgroundWarningColor - DESIGNABLE true) - Q_PROPERTY( - QColor backgroundErrorColor READ getBackgroundErrorColor WRITE setBackgroundErrorColor DESIGNABLE true) - Q_PROPERTY(QColor foregroundNominalColor READ getForegroundNominalColor WRITE setForegroundNominalColor - DESIGNABLE true) - Q_PROPERTY(QColor foregroundWarningColor READ getForegroundWarningColor WRITE setForegroundWarningColor - DESIGNABLE true) - Q_PROPERTY( - QColor foregroundErrorColor READ getForegroundErrorColor WRITE setForegroundErrorColor DESIGNABLE true) - - Q_PROPERTY(QColor backgroundNominalColorDisabled READ getBackgroundNominalColorDisabled WRITE - setBackgroundNominalColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor backgroundWarningColorDisabled READ getBackgroundWarningColorDisabled WRITE - setBackgroundWarningColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor backgroundErrorColorDisabled READ getBackgroundErrorColorDisabled WRITE - setBackgroundErrorColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundNominalColorDisabled READ getForegroundNominalColorDisabled WRITE - setForegroundNominalColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundWarningColorDisabled READ getForegroundWarningColorDisabled WRITE - setForegroundWarningColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundErrorColorDisabled READ getForegroundErrorColorDisabled WRITE - setForegroundErrorColorDisabled DESIGNABLE true) - - Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor DESIGNABLE true) - Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE setMagnitudeColor DESIGNABLE true) - Q_PROPERTY(QColor majorTickColor READ getMajorTickColor WRITE setMajorTickColor DESIGNABLE true) - Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE setMinorTickColor DESIGNABLE true) - Q_PROPERTY(int meterThickness READ getMeterThickness WRITE setMeterThickness DESIGNABLE true) - Q_PROPERTY(qreal meterFontScaling READ getMeterFontScaling WRITE setMeterFontScaling DESIGNABLE true) - - // Levels are denoted in dBFS. - Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel DESIGNABLE true) - Q_PROPERTY(qreal warningLevel READ getWarningLevel WRITE setWarningLevel DESIGNABLE true) - Q_PROPERTY(qreal errorLevel READ getErrorLevel WRITE setErrorLevel DESIGNABLE true) - Q_PROPERTY(qreal clipLevel READ getClipLevel WRITE setClipLevel DESIGNABLE true) - Q_PROPERTY(qreal minimumInputLevel READ getMinimumInputLevel WRITE setMinimumInputLevel DESIGNABLE true) - - // Rates are denoted in dB/second. - Q_PROPERTY(qreal peakDecayRate READ getPeakDecayRate WRITE setPeakDecayRate DESIGNABLE true) - - // Time in seconds for the VU meter to integrate over. - Q_PROPERTY(qreal magnitudeIntegrationTime READ getMagnitudeIntegrationTime WRITE setMagnitudeIntegrationTime - DESIGNABLE true) - - // Duration is denoted in seconds. - Q_PROPERTY(qreal peakHoldDuration READ getPeakHoldDuration WRITE setPeakHoldDuration DESIGNABLE true) - Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration WRITE setInputPeakHoldDuration - DESIGNABLE true) - - friend class VolControl; - -private: - obs_volmeter_t *obs_volmeter; - static std::weak_ptr updateTimer; - std::shared_ptr updateTimerRef; - - inline void resetLevels(); - inline void doLayout(); - inline bool detectIdle(uint64_t ts); - inline void calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw = 0.0); - inline void calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw); - - inline int convertToInt(float number); - void paintInputMeter(QPainter &painter, int x, int y, int width, int height, float peakHold); - void paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold); - void paintHTicks(QPainter &painter, int x, int y, int width); - void paintVMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold); - void paintVTicks(QPainter &painter, int x, int y, int height); - - QMutex dataMutex; - - bool recalculateLayout = true; - uint64_t currentLastUpdateTime = 0; - float currentMagnitude[MAX_AUDIO_CHANNELS]; - float currentPeak[MAX_AUDIO_CHANNELS]; - float currentInputPeak[MAX_AUDIO_CHANNELS]; - - int displayNrAudioChannels = 0; - float displayMagnitude[MAX_AUDIO_CHANNELS]; - float displayPeak[MAX_AUDIO_CHANNELS]; - float displayPeakHold[MAX_AUDIO_CHANNELS]; - uint64_t displayPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; - float displayInputPeakHold[MAX_AUDIO_CHANNELS]; - uint64_t displayInputPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; - - QFont tickFont; - QColor backgroundNominalColor; - QColor backgroundWarningColor; - QColor backgroundErrorColor; - QColor foregroundNominalColor; - QColor foregroundWarningColor; - QColor foregroundErrorColor; - - QColor backgroundNominalColorDisabled; - QColor backgroundWarningColorDisabled; - QColor backgroundErrorColorDisabled; - QColor foregroundNominalColorDisabled; - QColor foregroundWarningColorDisabled; - QColor foregroundErrorColorDisabled; - - QColor clipColor; - QColor magnitudeColor; - QColor majorTickColor; - QColor minorTickColor; - - int meterThickness; - qreal meterFontScaling; - - qreal minimumLevel; - qreal warningLevel; - qreal errorLevel; - qreal clipLevel; - qreal minimumInputLevel; - qreal peakDecayRate; - qreal magnitudeIntegrationTime; - qreal peakHoldDuration; - qreal inputPeakHoldDuration; - - QColor p_backgroundNominalColor; - QColor p_backgroundWarningColor; - QColor p_backgroundErrorColor; - QColor p_foregroundNominalColor; - QColor p_foregroundWarningColor; - QColor p_foregroundErrorColor; - - uint64_t lastRedrawTime = 0; - int channels = 0; - bool clipping = false; - bool vertical; - bool muted = false; - -public: - explicit VolumeMeter(QWidget *parent = nullptr, obs_volmeter_t *obs_volmeter = nullptr, bool vertical = false); - ~VolumeMeter(); - - void setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], - const float inputPeak[MAX_AUDIO_CHANNELS]); - QRect getBarRect() const; - bool needLayoutChange(); - - QColor getBackgroundNominalColor() const; - void setBackgroundNominalColor(QColor c); - QColor getBackgroundWarningColor() const; - void setBackgroundWarningColor(QColor c); - QColor getBackgroundErrorColor() const; - void setBackgroundErrorColor(QColor c); - QColor getForegroundNominalColor() const; - void setForegroundNominalColor(QColor c); - QColor getForegroundWarningColor() const; - void setForegroundWarningColor(QColor c); - QColor getForegroundErrorColor() const; - void setForegroundErrorColor(QColor c); - - QColor getBackgroundNominalColorDisabled() const; - void setBackgroundNominalColorDisabled(QColor c); - QColor getBackgroundWarningColorDisabled() const; - void setBackgroundWarningColorDisabled(QColor c); - QColor getBackgroundErrorColorDisabled() const; - void setBackgroundErrorColorDisabled(QColor c); - QColor getForegroundNominalColorDisabled() const; - void setForegroundNominalColorDisabled(QColor c); - QColor getForegroundWarningColorDisabled() const; - void setForegroundWarningColorDisabled(QColor c); - QColor getForegroundErrorColorDisabled() const; - void setForegroundErrorColorDisabled(QColor c); - - QColor getClipColor() const; - void setClipColor(QColor c); - QColor getMagnitudeColor() const; - void setMagnitudeColor(QColor c); - QColor getMajorTickColor() const; - void setMajorTickColor(QColor c); - QColor getMinorTickColor() const; - void setMinorTickColor(QColor c); - int getMeterThickness() const; - void setMeterThickness(int v); - qreal getMeterFontScaling() const; - void setMeterFontScaling(qreal v); - qreal getMinimumLevel() const; - void setMinimumLevel(qreal v); - qreal getWarningLevel() const; - void setWarningLevel(qreal v); - qreal getErrorLevel() const; - void setErrorLevel(qreal v); - qreal getClipLevel() const; - void setClipLevel(qreal v); - qreal getMinimumInputLevel() const; - void setMinimumInputLevel(qreal v); - qreal getPeakDecayRate() const; - void setPeakDecayRate(qreal v); - qreal getMagnitudeIntegrationTime() const; - void setMagnitudeIntegrationTime(qreal v); - qreal getPeakHoldDuration() const; - void setPeakHoldDuration(qreal v); - qreal getInputPeakHoldDuration() const; - void setInputPeakHoldDuration(qreal v); - void setPeakMeterType(enum obs_peak_meter_type peakMeterType); - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void wheelEvent(QWheelEvent *event) override; - -protected: - void paintEvent(QPaintEvent *event) override; - void changeEvent(QEvent *e) override; -}; - -class VolumeMeterTimer : public QTimer { - Q_OBJECT - -public: - inline VolumeMeterTimer() : QTimer() {} - - void AddVolControl(VolumeMeter *meter); - void RemoveVolControl(VolumeMeter *meter); - -protected: - void timerEvent(QTimerEvent *event) override; - QList volumeMeters; -}; - -class QLabel; +class OBSSourceLabel; +class VolumeMeter; class VolumeSlider; class MuteCheckBox; -class OBSSourceLabel; +class QLabel; +class QPushButton; class VolControl : public QFrame { Q_OBJECT @@ -298,44 +64,3 @@ public: void refreshColors(); }; - -class VolumeSlider : public AbsoluteSlider { - Q_OBJECT - -public: - obs_fader_t *fad; - - VolumeSlider(obs_fader_t *fader, QWidget *parent = nullptr); - VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, QWidget *parent = nullptr); - - bool getDisplayTicks() const; - void setDisplayTicks(bool display); - -private: - bool displayTicks = false; - QColor tickColor; - -protected: - virtual void paintEvent(QPaintEvent *event) override; -}; - -class VolumeAccessibleInterface : public QAccessibleWidget { - -public: - VolumeAccessibleInterface(QWidget *w); - - QVariant currentValue() const; - void setCurrentValue(const QVariant &value); - - QVariant maximumValue() const; - QVariant minimumValue() const; - - QVariant minimumStepSize() const; - -private: - VolumeSlider *slider() const; - -protected: - virtual QAccessible::Role role() const override; - virtual QString text(QAccessible::Text t) const override; -}; diff --git a/frontend/widgets/VolumeAccessibleInterface.cpp b/frontend/widgets/VolumeAccessibleInterface.cpp index cd00c14d7..ce7e496ab 100644 --- a/frontend/widgets/VolumeAccessibleInterface.cpp +++ b/frontend/widgets/VolumeAccessibleInterface.cpp @@ -1,1452 +1,6 @@ -#include "window-basic-main.hpp" -#include "moc_volume-control.cpp" -#include "obs-app.hpp" -#include "mute-checkbox.hpp" -#include "absolute-slider.hpp" -#include "source-label.hpp" +#include "VolumeAccessibleInterface.hpp" -#include -#include -#include -#include -#include -#include -#include - -using namespace std; - -#define FADER_PRECISION 4096.0 - -// Size of the audio indicator in pixels -#define INDICATOR_THICKNESS 3 - -// Padding on top and bottom of vertical meters -#define METER_PADDING 1 - -std::weak_ptr VolumeMeter::updateTimer; - -static inline Qt::CheckState GetCheckState(bool muted, bool unassigned) -{ - if (muted) - return Qt::Checked; - else if (unassigned) - return Qt::PartiallyChecked; - else - return Qt::Unchecked; -} - -static inline bool IsSourceUnassigned(obs_source_t *source) -{ - uint32_t mixes = (obs_source_get_audio_mixers(source) & ((1 << MAX_AUDIO_MIXES) - 1)); - obs_monitoring_type mt = obs_source_get_monitoring_type(source); - - return mixes == 0 && mt != OBS_MONITORING_TYPE_MONITOR_ONLY; -} - -static void ShowUnassignedWarning(const char *name) -{ - auto msgBox = [=]() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("VolControl.UnassignedWarning.Title")); - msgbox.setText(QTStr("VolControl.UnassignedWarning.Text").arg(name)); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); -} - -void VolControl::OBSVolumeChanged(void *data, float db) -{ - Q_UNUSED(db); - VolControl *volControl = static_cast(data); - - QMetaObject::invokeMethod(volControl, "VolumeChanged"); -} - -void VolControl::OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], - const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]) -{ - VolControl *volControl = static_cast(data); - - volControl->volMeter->setLevels(magnitude, peak, inputPeak); -} - -void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata) -{ - VolControl *volControl = static_cast(data); - bool muted = calldata_bool(calldata, "muted"); - - QMetaObject::invokeMethod(volControl, "VolumeMuted", Q_ARG(bool, muted)); -} - -void VolControl::VolumeChanged() -{ - slider->blockSignals(true); - slider->setValue((int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION)); - slider->blockSignals(false); - - updateText(); -} - -void VolControl::VolumeMuted(bool muted) -{ - bool unassigned = IsSourceUnassigned(source); - - auto newState = GetCheckState(muted, unassigned); - if (mute->checkState() != newState) - mute->setCheckState(newState); - - volMeter->muted = muted || unassigned; -} - -void VolControl::OBSMixersOrMonitoringChanged(void *data, calldata_t *) -{ - - VolControl *volControl = static_cast(data); - QMetaObject::invokeMethod(volControl, "MixersOrMonitoringChanged", Qt::QueuedConnection); -} - -void VolControl::MixersOrMonitoringChanged() -{ - bool muted = obs_source_muted(source); - bool unassigned = IsSourceUnassigned(source); - - auto newState = GetCheckState(muted, unassigned); - if (mute->checkState() != newState) - mute->setCheckState(newState); - - volMeter->muted = muted || unassigned; -} - -void VolControl::SetMuted(bool) -{ - bool checked = mute->checkState() == Qt::Checked; - bool prev = obs_source_muted(source); - obs_source_set_muted(source, checked); - bool unassigned = IsSourceUnassigned(source); - - if (!checked && unassigned) { - mute->setCheckState(Qt::PartiallyChecked); - /* Show notice about the source no being assigned to any tracks */ - bool has_shown_warning = - config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources"); - if (!has_shown_warning) - ShowUnassignedWarning(obs_source_get_name(source)); - } - - auto undo_redo = [](const std::string &uuid, bool val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_muted(source, val); - }; - - QString text = QTStr(checked ? "Undo.Volume.Mute" : "Undo.Volume.Unmute"); - - const char *name = obs_source_get_name(source); - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(text.arg(name), std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, checked), uuid, uuid); -} - -void VolControl::SliderChanged(int vol) -{ - float prev = obs_source_get_volume(source); - - obs_fader_set_deflection(obs_fader, float(vol) / FADER_PRECISION); - updateText(); - - auto undo_redo = [](const std::string &uuid, float val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_volume(source, val); - }; - - float val = obs_source_get_volume(source); - const char *name = obs_source_get_name(source); - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(QTStr("Undo.Volume.Change").arg(name), - std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, val), uuid, uuid, true); -} - -void VolControl::updateText() -{ - QString text; - float db = obs_fader_get_db(obs_fader); - - if (db < -96.0f) - text = "-inf dB"; - else - text = QString::number(db, 'f', 1).append(" dB"); - - volLabel->setText(text); - - bool muted = obs_source_muted(source); - const char *accTextLookup = muted ? "VolControl.SliderMuted" : "VolControl.SliderUnmuted"; - - QString sourceName = obs_source_get_name(source); - QString accText = QTStr(accTextLookup).arg(sourceName); - - slider->setAccessibleName(accText); -} - -void VolControl::EmitConfigClicked() -{ - emit ConfigClicked(); -} - -void VolControl::SetMeterDecayRate(qreal q) -{ - volMeter->setPeakDecayRate(q); -} - -void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType) -{ - volMeter->setPeakMeterType(peakMeterType); -} - -VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) - : source(std::move(source_)), - levelTotal(0.0f), - levelCount(0.0f), - obs_fader(obs_fader_create(OBS_FADER_LOG)), - obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)), - vertical(vertical), - contextMenu(nullptr) -{ - nameLabel = new OBSSourceLabel(source); - volLabel = new QLabel(); - mute = new MuteCheckBox(); - - volLabel->setObjectName("volLabel"); - volLabel->setAlignment(Qt::AlignCenter); - -#ifdef __APPLE__ - mute->setAttribute(Qt::WA_LayoutUsesWidgetRect); -#endif - - QString sourceName = obs_source_get_name(source); - setObjectName(sourceName); - - if (showConfig) { - config = new QPushButton(this); - config->setProperty("class", "icon-dots-vert"); - config->setAutoDefault(false); - - config->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - config->setAccessibleName(QTStr("VolControl.Properties").arg(sourceName)); - - connect(config, &QAbstractButton::clicked, this, &VolControl::EmitConfigClicked); - } - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(0); - - if (vertical) { - QHBoxLayout *nameLayout = new QHBoxLayout; - QHBoxLayout *controlLayout = new QHBoxLayout; - QHBoxLayout *volLayout = new QHBoxLayout; - QFrame *meterFrame = new QFrame; - QHBoxLayout *meterLayout = new QHBoxLayout; - - volMeter = new VolumeMeter(nullptr, obs_volmeter, true); - slider = new VolumeSlider(obs_fader, Qt::Vertical); - slider->setLayoutDirection(Qt::LeftToRight); - slider->setDisplayTicks(true); - - nameLayout->setAlignment(Qt::AlignCenter); - meterLayout->setAlignment(Qt::AlignCenter); - controlLayout->setAlignment(Qt::AlignCenter); - volLayout->setAlignment(Qt::AlignCenter); - - meterFrame->setObjectName("volMeterFrame"); - - nameLayout->setContentsMargins(0, 0, 0, 0); - nameLayout->setSpacing(0); - nameLayout->addWidget(nameLabel); - - controlLayout->setContentsMargins(0, 0, 0, 0); - controlLayout->setSpacing(0); - - // Add Headphone (audio monitoring) widget here - controlLayout->addWidget(mute); - - if (showConfig) { - controlLayout->addWidget(config); - } - - meterLayout->setContentsMargins(0, 0, 0, 0); - meterLayout->setSpacing(0); - meterLayout->addWidget(slider); - meterLayout->addWidget(volMeter); - - meterFrame->setLayout(meterLayout); - - volLayout->setContentsMargins(0, 0, 0, 0); - volLayout->setSpacing(0); - volLayout->addWidget(volLabel); - volLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum)); - - mainLayout->addItem(nameLayout); - mainLayout->addItem(volLayout); - mainLayout->addWidget(meterFrame); - mainLayout->addItem(controlLayout); - - volMeter->setFocusProxy(slider); - - // Default size can cause clipping of long names in vertical layout. - QFont font = nameLabel->font(); - QFontInfo info(font); - nameLabel->setFont(font); - - setMaximumWidth(110); - } else { - QHBoxLayout *textLayout = new QHBoxLayout; - QHBoxLayout *controlLayout = new QHBoxLayout; - QFrame *meterFrame = new QFrame; - QVBoxLayout *meterLayout = new QVBoxLayout; - QVBoxLayout *buttonLayout = new QVBoxLayout; - - volMeter = new VolumeMeter(nullptr, obs_volmeter, false); - volMeter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - - slider = new VolumeSlider(obs_fader, Qt::Horizontal); - slider->setLayoutDirection(Qt::LeftToRight); - slider->setDisplayTicks(true); - - textLayout->setContentsMargins(0, 0, 0, 0); - textLayout->addWidget(nameLabel); - textLayout->addWidget(volLabel); - textLayout->setAlignment(nameLabel, Qt::AlignLeft); - textLayout->setAlignment(volLabel, Qt::AlignRight); - - meterFrame->setObjectName("volMeterFrame"); - meterFrame->setLayout(meterLayout); - - meterLayout->setContentsMargins(0, 0, 0, 0); - meterLayout->setSpacing(0); - - meterLayout->addWidget(volMeter); - meterLayout->addWidget(slider); - - buttonLayout->setContentsMargins(0, 0, 0, 0); - buttonLayout->setSpacing(0); - - if (showConfig) { - buttonLayout->addWidget(config); - } - buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)); - buttonLayout->addWidget(mute); - - controlLayout->addItem(buttonLayout); - controlLayout->addWidget(meterFrame); - - mainLayout->addItem(textLayout); - mainLayout->addItem(controlLayout); - - volMeter->setFocusProxy(slider); - } - - setLayout(mainLayout); - - nameLabel->setText(sourceName); - - slider->setMinimum(0); - slider->setMaximum(int(FADER_PRECISION)); - - bool muted = obs_source_muted(source); - bool unassigned = IsSourceUnassigned(source); - mute->setCheckState(GetCheckState(muted, unassigned)); - volMeter->muted = muted || unassigned; - mute->setAccessibleName(QTStr("VolControl.Mute").arg(sourceName)); - obs_fader_add_callback(obs_fader, OBSVolumeChanged, this); - obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this); - - sigs.emplace_back(obs_source_get_signal_handler(source), "mute", OBSVolumeMuted, this); - sigs.emplace_back(obs_source_get_signal_handler(source), "audio_mixers", OBSMixersOrMonitoringChanged, this); - sigs.emplace_back(obs_source_get_signal_handler(source), "audio_monitoring", OBSMixersOrMonitoringChanged, - this); - - QWidget::connect(slider, &VolumeSlider::valueChanged, this, &VolControl::SliderChanged); - QWidget::connect(mute, &MuteCheckBox::clicked, this, &VolControl::SetMuted); - - obs_fader_attach_source(obs_fader, source); - obs_volmeter_attach_source(obs_volmeter, source); - - /* Call volume changed once to init the slider position and label */ - VolumeChanged(); -} - -void VolControl::EnableSlider(bool enable) -{ - slider->setEnabled(enable); -} - -VolControl::~VolControl() -{ - obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this); - obs_volmeter_remove_callback(obs_volmeter, OBSVolumeLevel, this); - - sigs.clear(); - - if (contextMenu) - contextMenu->close(); -} - -static inline QColor color_from_int(long long val) -{ - QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); - color.setAlpha(255); - - return color; -} - -QColor VolumeMeter::getBackgroundNominalColor() const -{ - return p_backgroundNominalColor; -} - -QColor VolumeMeter::getBackgroundNominalColorDisabled() const -{ - return backgroundNominalColorDisabled; -} - -void VolumeMeter::setBackgroundNominalColor(QColor c) -{ - p_backgroundNominalColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundNominalColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreen")); - } else { - backgroundNominalColor = p_backgroundNominalColor; - } -} - -void VolumeMeter::setBackgroundNominalColorDisabled(QColor c) -{ - backgroundNominalColorDisabled = std::move(c); -} - -QColor VolumeMeter::getBackgroundWarningColor() const -{ - return p_backgroundWarningColor; -} - -QColor VolumeMeter::getBackgroundWarningColorDisabled() const -{ - return backgroundWarningColorDisabled; -} - -void VolumeMeter::setBackgroundWarningColor(QColor c) -{ - p_backgroundWarningColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundWarningColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellow")); - } else { - backgroundWarningColor = p_backgroundWarningColor; - } -} - -void VolumeMeter::setBackgroundWarningColorDisabled(QColor c) -{ - backgroundWarningColorDisabled = std::move(c); -} - -QColor VolumeMeter::getBackgroundErrorColor() const -{ - return p_backgroundErrorColor; -} - -QColor VolumeMeter::getBackgroundErrorColorDisabled() const -{ - return backgroundErrorColorDisabled; -} - -void VolumeMeter::setBackgroundErrorColor(QColor c) -{ - p_backgroundErrorColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - backgroundErrorColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerRed")); - } else { - backgroundErrorColor = p_backgroundErrorColor; - } -} - -void VolumeMeter::setBackgroundErrorColorDisabled(QColor c) -{ - backgroundErrorColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundNominalColor() const -{ - return p_foregroundNominalColor; -} - -QColor VolumeMeter::getForegroundNominalColorDisabled() const -{ - return foregroundNominalColorDisabled; -} - -void VolumeMeter::setForegroundNominalColor(QColor c) -{ - p_foregroundNominalColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundNominalColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerGreenActive")); - } else { - foregroundNominalColor = p_foregroundNominalColor; - } -} - -void VolumeMeter::setForegroundNominalColorDisabled(QColor c) -{ - foregroundNominalColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundWarningColor() const -{ - return p_foregroundWarningColor; -} - -QColor VolumeMeter::getForegroundWarningColorDisabled() const -{ - return foregroundWarningColorDisabled; -} - -void VolumeMeter::setForegroundWarningColor(QColor c) -{ - p_foregroundWarningColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundWarningColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerYellowActive")); - } else { - foregroundWarningColor = p_foregroundWarningColor; - } -} - -void VolumeMeter::setForegroundWarningColorDisabled(QColor c) -{ - foregroundWarningColorDisabled = std::move(c); -} - -QColor VolumeMeter::getForegroundErrorColor() const -{ - return p_foregroundErrorColor; -} - -QColor VolumeMeter::getForegroundErrorColorDisabled() const -{ - return foregroundErrorColorDisabled; -} - -void VolumeMeter::setForegroundErrorColor(QColor c) -{ - p_foregroundErrorColor = std::move(c); - - if (config_get_bool(App()->GetUserConfig(), "Accessibility", "OverrideColors")) { - foregroundErrorColor = - color_from_int(config_get_int(App()->GetUserConfig(), "Accessibility", "MixerRedActive")); - } else { - foregroundErrorColor = p_foregroundErrorColor; - } -} - -void VolumeMeter::setForegroundErrorColorDisabled(QColor c) -{ - foregroundErrorColorDisabled = std::move(c); -} - -QColor VolumeMeter::getClipColor() const -{ - return clipColor; -} - -void VolumeMeter::setClipColor(QColor c) -{ - clipColor = std::move(c); -} - -QColor VolumeMeter::getMagnitudeColor() const -{ - return magnitudeColor; -} - -void VolumeMeter::setMagnitudeColor(QColor c) -{ - magnitudeColor = std::move(c); -} - -QColor VolumeMeter::getMajorTickColor() const -{ - return majorTickColor; -} - -void VolumeMeter::setMajorTickColor(QColor c) -{ - majorTickColor = std::move(c); -} - -QColor VolumeMeter::getMinorTickColor() const -{ - return minorTickColor; -} - -void VolumeMeter::setMinorTickColor(QColor c) -{ - minorTickColor = std::move(c); -} - -int VolumeMeter::getMeterThickness() const -{ - return meterThickness; -} - -void VolumeMeter::setMeterThickness(int v) -{ - meterThickness = v; - recalculateLayout = true; -} - -qreal VolumeMeter::getMeterFontScaling() const -{ - return meterFontScaling; -} - -void VolumeMeter::setMeterFontScaling(qreal v) -{ - meterFontScaling = v; - recalculateLayout = true; -} - -void VolControl::refreshColors() -{ - volMeter->setBackgroundNominalColor(volMeter->getBackgroundNominalColor()); - volMeter->setBackgroundWarningColor(volMeter->getBackgroundWarningColor()); - volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor()); - volMeter->setForegroundNominalColor(volMeter->getForegroundNominalColor()); - volMeter->setForegroundWarningColor(volMeter->getForegroundWarningColor()); - volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor()); -} - -qreal VolumeMeter::getMinimumLevel() const -{ - return minimumLevel; -} - -void VolumeMeter::setMinimumLevel(qreal v) -{ - minimumLevel = v; -} - -qreal VolumeMeter::getWarningLevel() const -{ - return warningLevel; -} - -void VolumeMeter::setWarningLevel(qreal v) -{ - warningLevel = v; -} - -qreal VolumeMeter::getErrorLevel() const -{ - return errorLevel; -} - -void VolumeMeter::setErrorLevel(qreal v) -{ - errorLevel = v; -} - -qreal VolumeMeter::getClipLevel() const -{ - return clipLevel; -} - -void VolumeMeter::setClipLevel(qreal v) -{ - clipLevel = v; -} - -qreal VolumeMeter::getMinimumInputLevel() const -{ - return minimumInputLevel; -} - -void VolumeMeter::setMinimumInputLevel(qreal v) -{ - minimumInputLevel = v; -} - -qreal VolumeMeter::getPeakDecayRate() const -{ - return peakDecayRate; -} - -void VolumeMeter::setPeakDecayRate(qreal v) -{ - peakDecayRate = v; -} - -qreal VolumeMeter::getMagnitudeIntegrationTime() const -{ - return magnitudeIntegrationTime; -} - -void VolumeMeter::setMagnitudeIntegrationTime(qreal v) -{ - magnitudeIntegrationTime = v; -} - -qreal VolumeMeter::getPeakHoldDuration() const -{ - return peakHoldDuration; -} - -void VolumeMeter::setPeakHoldDuration(qreal v) -{ - peakHoldDuration = v; -} - -qreal VolumeMeter::getInputPeakHoldDuration() const -{ - return inputPeakHoldDuration; -} - -void VolumeMeter::setInputPeakHoldDuration(qreal v) -{ - inputPeakHoldDuration = v; -} - -void VolumeMeter::setPeakMeterType(enum obs_peak_meter_type peakMeterType) -{ - obs_volmeter_set_peak_meter_type(obs_volmeter, peakMeterType); - switch (peakMeterType) { - case TRUE_PEAK_METER: - // For true-peak meters EBU has defined the Permitted Maximum, - // taking into account the accuracy of the meter and further - // processing required by lossy audio compression. - // - // The alignment level was not specified, but I've adjusted - // it compared to a sample-peak meter. Incidentally Youtube - // uses this new Alignment Level as the maximum integrated - // loudness of a video. - // - // * Permitted Maximum Level (PML) = -2.0 dBTP - // * Alignment Level (AL) = -13 dBTP - setErrorLevel(-2.0); - setWarningLevel(-13.0); - break; - - case SAMPLE_PEAK_METER: - default: - // For a sample Peak Meter EBU has the following level - // definitions, taking into account inaccuracies of this meter: - // - // * Permitted Maximum Level (PML) = -9.0 dBFS - // * Alignment Level (AL) = -20.0 dBFS - setErrorLevel(-9.0); - setWarningLevel(-20.0); - break; - } -} - -void VolumeMeter::mousePressEvent(QMouseEvent *event) -{ - setFocus(Qt::MouseFocusReason); - event->accept(); -} - -void VolumeMeter::wheelEvent(QWheelEvent *event) -{ - QApplication::sendEvent(focusProxy(), event); -} - -VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter, bool vertical) - : QWidget(parent), - obs_volmeter(obs_volmeter), - vertical(vertical) -{ - setAttribute(Qt::WA_OpaquePaintEvent, true); - - // Default meter settings, they only show if - // there is no stylesheet, do not remove. - backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green - backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow - backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red - foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green - foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow - foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red - - backgroundNominalColorDisabled.setRgb(90, 90, 90); - backgroundWarningColorDisabled.setRgb(117, 117, 117); - backgroundErrorColorDisabled.setRgb(65, 65, 65); - foregroundNominalColorDisabled.setRgb(163, 163, 163); - foregroundWarningColorDisabled.setRgb(217, 217, 217); - foregroundErrorColorDisabled.setRgb(113, 113, 113); - - clipColor.setRgb(0xff, 0xff, 0xff); // Bright white - magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black - majorTickColor.setRgb(0x00, 0x00, 0x00); // Black - minorTickColor.setRgb(0x32, 0x32, 0x32); // Dark gray - minimumLevel = -60.0; // -60 dB - warningLevel = -20.0; // -20 dB - errorLevel = -9.0; // -9 dB - clipLevel = -0.5; // -0.5 dB - minimumInputLevel = -50.0; // -50 dB - peakDecayRate = 11.76; // 20 dB / 1.7 sec - magnitudeIntegrationTime = 0.3; // 99% in 300 ms - peakHoldDuration = 20.0; // 20 seconds - inputPeakHoldDuration = 1.0; // 1 second - meterThickness = 3; // Bar thickness in pixels - meterFontScaling = 0.7; // Font size for numbers is 70% of Widget's font size - channels = (int)audio_output_get_channels(obs_get_audio()); - - doLayout(); - updateTimerRef = updateTimer.lock(); - if (!updateTimerRef) { - updateTimerRef = std::make_shared(); - updateTimerRef->setTimerType(Qt::PreciseTimer); - updateTimerRef->start(16); - updateTimer = updateTimerRef; - } - - updateTimerRef->AddVolControl(this); -} - -VolumeMeter::~VolumeMeter() -{ - updateTimerRef->RemoveVolControl(this); -} - -void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], - const float inputPeak[MAX_AUDIO_CHANNELS]) -{ - uint64_t ts = os_gettime_ns(); - QMutexLocker locker(&dataMutex); - - currentLastUpdateTime = ts; - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { - currentMagnitude[channelNr] = magnitude[channelNr]; - currentPeak[channelNr] = peak[channelNr]; - currentInputPeak[channelNr] = inputPeak[channelNr]; - } - - // In case there are more updates then redraws we must make sure - // that the ballistics of peak and hold are recalculated. - locker.unlock(); - calculateBallistics(ts); -} - -inline void VolumeMeter::resetLevels() -{ - currentLastUpdateTime = 0; - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { - currentMagnitude[channelNr] = -M_INFINITE; - currentPeak[channelNr] = -M_INFINITE; - currentInputPeak[channelNr] = -M_INFINITE; - - displayMagnitude[channelNr] = -M_INFINITE; - displayPeak[channelNr] = -M_INFINITE; - displayPeakHold[channelNr] = -M_INFINITE; - displayPeakHoldLastUpdateTime[channelNr] = 0; - displayInputPeakHold[channelNr] = -M_INFINITE; - displayInputPeakHoldLastUpdateTime[channelNr] = 0; - } -} - -bool VolumeMeter::needLayoutChange() -{ - int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); - - if (!currentNrAudioChannels) { - struct obs_audio_info oai; - obs_get_audio_info(&oai); - currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1 : 2; - } - - if (displayNrAudioChannels != currentNrAudioChannels) { - displayNrAudioChannels = currentNrAudioChannels; - recalculateLayout = true; - } - - return recalculateLayout; -} - -// When this is called from the constructor, obs_volmeter_get_nr_channels has not -// yet been called and Q_PROPERTY settings have not yet been read from the -// stylesheet. -inline void VolumeMeter::doLayout() -{ - QMutexLocker locker(&dataMutex); - - if (displayNrAudioChannels) { - int meterSize = std::floor(22 / displayNrAudioChannels); - setMeterThickness(std::clamp(meterSize, 3, 7)); - } - recalculateLayout = false; - - tickFont = font(); - QFontInfo info(tickFont); - tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling); - QFontMetrics metrics(tickFont); - if (vertical) { - // Each meter channel is meterThickness pixels wide, plus one pixel - // between channels, but not after the last. - // Add 4 pixels for ticks, space to hold our longest label in this font, - // and a few pixels before the fader. - QRect scaleBounds = metrics.boundingRect("-88"); - setMinimumSize(displayNrAudioChannels * (meterThickness + 1) - 1 + 10 + scaleBounds.width() + 2, 100); - } else { - // Each meter channel is meterThickness pixels high, plus one pixel - // between channels, but not after the last. - // Add 4 pixels for ticks, and space high enough to hold our label in - // this font, presuming that digits don't have descenders. - setMinimumSize(100, displayNrAudioChannels * (meterThickness + 1) - 1 + 4 + metrics.capHeight()); - } - - resetLevels(); -} - -inline bool VolumeMeter::detectIdle(uint64_t ts) -{ - double timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001; - if (timeSinceLastUpdate > 0.5) { - resetLevels(); - return true; - } else { - return false; - } -} - -inline void VolumeMeter::calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw) -{ - if (currentPeak[channelNr] >= displayPeak[channelNr] || isnan(displayPeak[channelNr])) { - // Attack of peak is immediate. - displayPeak[channelNr] = currentPeak[channelNr]; - } else { - // Decay of peak is 40 dB / 1.7 seconds for Fast Profile - // 20 dB / 1.7 seconds for Medium Profile (Type I PPM) - // 24 dB / 2.8 seconds for Slow Profile (Type II PPM) - float decay = float(peakDecayRate * timeSinceLastRedraw); - displayPeak[channelNr] = - std::clamp(displayPeak[channelNr] - decay, std::min(currentPeak[channelNr], 0.f), 0.f); - } - - if (currentPeak[channelNr] >= displayPeakHold[channelNr] || !isfinite(displayPeakHold[channelNr])) { - // Attack of peak-hold is immediate, but keep track - // when it was last updated. - displayPeakHold[channelNr] = currentPeak[channelNr]; - displayPeakHoldLastUpdateTime[channelNr] = ts; - } else { - // The peak and hold falls back to peak - // after 20 seconds. - qreal timeSinceLastPeak = (uint64_t)(ts - displayPeakHoldLastUpdateTime[channelNr]) * 0.000000001; - if (timeSinceLastPeak > peakHoldDuration) { - displayPeakHold[channelNr] = currentPeak[channelNr]; - displayPeakHoldLastUpdateTime[channelNr] = ts; - } - } - - if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] || - !isfinite(displayInputPeakHold[channelNr])) { - // Attack of peak-hold is immediate, but keep track - // when it was last updated. - displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; - displayInputPeakHoldLastUpdateTime[channelNr] = ts; - } else { - // The peak and hold falls back to peak after 1 second. - qreal timeSinceLastPeak = (uint64_t)(ts - displayInputPeakHoldLastUpdateTime[channelNr]) * 0.000000001; - if (timeSinceLastPeak > inputPeakHoldDuration) { - displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; - displayInputPeakHoldLastUpdateTime[channelNr] = ts; - } - } - - if (!isfinite(displayMagnitude[channelNr])) { - // The statements in the else-leg do not work with - // NaN and infinite displayMagnitude. - displayMagnitude[channelNr] = currentMagnitude[channelNr]; - } else { - // A VU meter will integrate to the new value to 99% in 300 ms. - // The calculation here is very simplified and is more accurate - // with higher frame-rate. - float attack = float((currentMagnitude[channelNr] - displayMagnitude[channelNr]) * - (timeSinceLastRedraw / magnitudeIntegrationTime) * 0.99); - displayMagnitude[channelNr] = - std::clamp(displayMagnitude[channelNr] + attack, (float)minimumLevel, 0.f); - } -} - -inline void VolumeMeter::calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw) -{ - QMutexLocker locker(&dataMutex); - - for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) - calculateBallisticsForChannel(channelNr, ts, timeSinceLastRedraw); -} - -void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width, int height, float peakHold) -{ - QMutexLocker locker(&dataMutex); - QColor color; - - if (peakHold < minimumInputLevel) - color = backgroundNominalColor; - else if (peakHold < warningLevel) - color = foregroundNominalColor; - else if (peakHold < errorLevel) - color = foregroundWarningColor; - else if (peakHold <= clipLevel) - color = foregroundErrorColor; - else - color = clipColor; - - painter.fillRect(x, y, width, height, color); -} - -void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width) -{ - qreal scale = width / minimumLevel; - - painter.setFont(tickFont); - QFontMetrics metrics(tickFont); - painter.setPen(majorTickColor); - - // Draw major tick lines and numeric indicators. - for (int i = 0; i >= minimumLevel; i -= 5) { - int position = int(x + width - (i * scale) - 1); - QString str = QString::number(i); - - // Center the number on the tick, but don't overflow - QRect textBounds = metrics.boundingRect(str); - int pos; - if (i == 0) { - pos = position - textBounds.width(); - } else { - pos = position - (textBounds.width() / 2); - if (pos < 0) - pos = 0; - } - painter.drawText(pos, y + 4 + metrics.capHeight(), str); - - painter.drawLine(position, y, position, y + 2); - } -} - -void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height) -{ - qreal scale = height / minimumLevel; - - painter.setFont(tickFont); - QFontMetrics metrics(tickFont); - painter.setPen(majorTickColor); - - // Draw major tick lines and numeric indicators. - for (int i = 0; i >= minimumLevel; i -= 5) { - int position = y + int(i * scale) + METER_PADDING; - QString str = QString::number(i); - - // Center the number on the tick, but don't overflow - if (i == 0) { - painter.drawText(x + 10, position + metrics.capHeight(), str); - } else { - painter.drawText(x + 8, position + (metrics.capHeight() / 2), str); - } - - painter.drawLine(x, position, x + 2, position); - } -} - -#define CLIP_FLASH_DURATION_MS 1000 - -inline int VolumeMeter::convertToInt(float number) -{ - constexpr int min = std::numeric_limits::min(); - constexpr int max = std::numeric_limits::max(); - - // NOTE: Conversion from 'const int' to 'float' changes max value from 2147483647 to 2147483648 - if (number >= (float)max) - return max; - else if (number < min) - return min; - else - return int(number); -} - -void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold) -{ - qreal scale = width / minimumLevel; - - QMutexLocker locker(&dataMutex); - int minimumPosition = x + 0; - int maximumPosition = x + width; - int magnitudePosition = x + width - convertToInt(magnitude * scale); - int peakPosition = x + width - convertToInt(peak * scale); - int peakHoldPosition = x + width - convertToInt(peakHold * scale); - int warningPosition = x + width - convertToInt(warningLevel * scale); - int errorPosition = x + width - convertToInt(errorLevel * scale); - - int nominalLength = warningPosition - minimumPosition; - int warningLength = errorPosition - warningPosition; - int errorLength = maximumPosition - errorPosition; - locker.unlock(); - - if (clipping) { - peakPosition = maximumPosition; - } - - if (peakPosition < minimumPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < warningPosition) { - painter.fillRect(minimumPosition, y, peakPosition - minimumPosition, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(peakPosition, y, warningPosition - peakPosition, height, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < errorPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(warningPosition, y, peakPosition - warningPosition, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(peakPosition, y, errorPosition - peakPosition, height, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(errorPosition, y, errorLength, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < maximumPosition) { - painter.fillRect(minimumPosition, y, nominalLength, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(warningPosition, y, warningLength, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(errorPosition, y, peakPosition - errorPosition, height, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - painter.fillRect(peakPosition, y, maximumPosition - peakPosition, height, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else { - if (!clipping) { - QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, [&]() { clipping = false; }); - clipping = true; - } - - int end = errorLength + warningLength + nominalLength; - painter.fillRect(minimumPosition, y, end, height, - QBrush(muted ? foregroundErrorColorDisabled : foregroundErrorColor)); - } - - if (peakHoldPosition - 3 < minimumPosition) - ; // Peak-hold below minimum, no drawing. - else if (peakHoldPosition < warningPosition) - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - else if (peakHoldPosition < errorPosition) - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - else - painter.fillRect(peakHoldPosition - 3, y, 3, height, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - - if (magnitudePosition - 3 >= minimumPosition) - painter.fillRect(magnitudePosition - 3, y, 3, height, magnitudeColor); -} - -void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold) -{ - qreal scale = height / minimumLevel; - - QMutexLocker locker(&dataMutex); - int minimumPosition = y + 0; - int maximumPosition = y + height; - int magnitudePosition = y + height - convertToInt(magnitude * scale); - int peakPosition = y + height - convertToInt(peak * scale); - int peakHoldPosition = y + height - convertToInt(peakHold * scale); - int warningPosition = y + height - convertToInt(warningLevel * scale); - int errorPosition = y + height - convertToInt(errorLevel * scale); - - int nominalLength = warningPosition - minimumPosition; - int warningLength = errorPosition - warningPosition; - int errorLength = maximumPosition - errorPosition; - locker.unlock(); - - if (clipping) { - peakPosition = maximumPosition; - } - - if (peakPosition < minimumPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < warningPosition) { - painter.fillRect(x, minimumPosition, width, peakPosition - minimumPosition, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, peakPosition, width, warningPosition - peakPosition, - muted ? backgroundNominalColorDisabled : backgroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < errorPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, warningPosition, width, peakPosition - warningPosition, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(x, peakPosition, width, errorPosition - peakPosition, - muted ? backgroundWarningColorDisabled : backgroundWarningColor); - painter.fillRect(x, errorPosition, width, errorLength, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else if (peakPosition < maximumPosition) { - painter.fillRect(x, minimumPosition, width, nominalLength, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - painter.fillRect(x, warningPosition, width, warningLength, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - painter.fillRect(x, errorPosition, width, peakPosition - errorPosition, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - painter.fillRect(x, peakPosition, width, maximumPosition - peakPosition, - muted ? backgroundErrorColorDisabled : backgroundErrorColor); - } else { - if (!clipping) { - QTimer::singleShot(CLIP_FLASH_DURATION_MS, this, [&]() { clipping = false; }); - clipping = true; - } - - int end = errorLength + warningLength + nominalLength; - painter.fillRect(x, minimumPosition, width, end, - QBrush(muted ? foregroundErrorColorDisabled : foregroundErrorColor)); - } - - if (peakHoldPosition - 3 < minimumPosition) - ; // Peak-hold below minimum, no drawing. - else if (peakHoldPosition < warningPosition) - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundNominalColorDisabled : foregroundNominalColor); - else if (peakHoldPosition < errorPosition) - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundWarningColorDisabled : foregroundWarningColor); - else - painter.fillRect(x, peakHoldPosition - 3, width, 3, - muted ? foregroundErrorColorDisabled : foregroundErrorColor); - - if (magnitudePosition - 3 >= minimumPosition) - painter.fillRect(x, magnitudePosition - 3, width, 3, magnitudeColor); -} - -void VolumeMeter::paintEvent(QPaintEvent *event) -{ - uint64_t ts = os_gettime_ns(); - qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; - calculateBallistics(ts, timeSinceLastRedraw); - bool idle = detectIdle(ts); - - QRect widgetRect = rect(); - int width = widgetRect.width(); - int height = widgetRect.height(); - - QPainter painter(this); - - // Paint window background color (as widget is opaque) - QColor background = palette().color(QPalette::ColorRole::Window); - painter.fillRect(event->region().boundingRect(), background); - - if (vertical) - height -= METER_PADDING * 2; - - // timerEvent requests update of the bar(s) only, so we can avoid the - // overhead of repainting the scale and labels. - if (event->region().boundingRect() != getBarRect()) { - if (needLayoutChange()) - doLayout(); - - if (vertical) { - paintVTicks(painter, displayNrAudioChannels * (meterThickness + 1) - 1, 0, - height - (INDICATOR_THICKNESS + 3)); - } else { - paintHTicks(painter, INDICATOR_THICKNESS + 3, displayNrAudioChannels * (meterThickness + 1) - 1, - width - (INDICATOR_THICKNESS + 3)); - } - } - - if (vertical) { - // Invert the Y axis to ease the math - painter.translate(0, height + METER_PADDING); - painter.scale(1, -1); - } - - for (int channelNr = 0; channelNr < displayNrAudioChannels; channelNr++) { - - int channelNrFixed = (displayNrAudioChannels == 1 && channels > 2) ? 2 : channelNr; - - if (vertical) - paintVMeter(painter, channelNr * (meterThickness + 1), INDICATOR_THICKNESS + 2, meterThickness, - height - (INDICATOR_THICKNESS + 2), displayMagnitude[channelNrFixed], - displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); - else - paintHMeter(painter, INDICATOR_THICKNESS + 2, channelNr * (meterThickness + 1), - width - (INDICATOR_THICKNESS + 2), meterThickness, displayMagnitude[channelNrFixed], - displayPeak[channelNrFixed], displayPeakHold[channelNrFixed]); - - if (idle) - continue; - - // By not drawing the input meter boxes the user can - // see that the audio stream has been stopped, without - // having too much visual impact. - if (vertical) - paintInputMeter(painter, channelNr * (meterThickness + 1), 0, meterThickness, - INDICATOR_THICKNESS, displayInputPeakHold[channelNrFixed]); - else - paintInputMeter(painter, 0, channelNr * (meterThickness + 1), INDICATOR_THICKNESS, - meterThickness, displayInputPeakHold[channelNrFixed]); - } - - lastRedrawTime = ts; -} - -QRect VolumeMeter::getBarRect() const -{ - QRect rec = rect(); - if (vertical) - rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1); - else - rec.setHeight(displayNrAudioChannels * (meterThickness + 1) - 1); - - return rec; -} - -void VolumeMeter::changeEvent(QEvent *e) -{ - if (e->type() == QEvent::StyleChange) - recalculateLayout = true; - - QWidget::changeEvent(e); -} - -void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) -{ - volumeMeters.push_back(meter); -} - -void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter) -{ - volumeMeters.removeOne(meter); -} - -void VolumeMeterTimer::timerEvent(QTimerEvent *) -{ - for (VolumeMeter *meter : volumeMeters) { - if (meter->needLayoutChange()) { - // Tell paintEvent to update layout and paint everything - meter->update(); - } else { - // Tell paintEvent to paint only the bars - meter->update(meter->getBarRect()); - } - } -} - -VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent) : AbsoluteSlider(parent) -{ - fad = fader; -} - -VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, QWidget *parent) - : AbsoluteSlider(orientation, parent) -{ - fad = fader; -} - -bool VolumeSlider::getDisplayTicks() const -{ - return displayTicks; -} - -void VolumeSlider::setDisplayTicks(bool display) -{ - displayTicks = display; -} - -void VolumeSlider::paintEvent(QPaintEvent *event) -{ - if (!getDisplayTicks()) { - QSlider::paintEvent(event); - return; - } - - QPainter painter(this); - QColor tickColor(91, 98, 115, 255); - - obs_fader_conversion_t fader_db_to_def = obs_fader_db_to_def(fad); - - QStyleOptionSlider opt; - initStyleOption(&opt); - - QRect groove = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); - QRect handle = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); - - if (orientation() == Qt::Horizontal) { - const int sliderWidth = groove.width() - handle.width(); - - float tickLength = groove.height() * 1.5; - tickLength = std::max((int)tickLength + groove.height(), 8 + groove.height()); - - float yPos = groove.center().y() - (tickLength / 2) + 1; - - for (int db = -10; db >= -90; db -= 10) { - float tickValue = fader_db_to_def(db); - - float xPos = groove.left() + (tickValue * sliderWidth) + (handle.width() / 2); - painter.fillRect(xPos, yPos, 1, tickLength, tickColor); - } - } - - if (orientation() == Qt::Vertical) { - const int sliderHeight = groove.height() - handle.height(); - - float tickLength = groove.width() * 1.5; - tickLength = std::max((int)tickLength + groove.width(), 8 + groove.width()); - - float xPos = groove.center().x() - (tickLength / 2) + 1; - - for (int db = -10; db >= -96; db -= 10) { - float tickValue = fader_db_to_def(db); - - float yPos = - groove.height() + groove.top() - (tickValue * sliderHeight) - (handle.height() / 2); - painter.fillRect(xPos, yPos, tickLength, 1, tickColor); - } - } - - QSlider::paintEvent(event); -} +#include VolumeAccessibleInterface::VolumeAccessibleInterface(QWidget *w) : QAccessibleWidget(w) {} diff --git a/frontend/widgets/VolumeAccessibleInterface.hpp b/frontend/widgets/VolumeAccessibleInterface.hpp index 51c77f247..2de609162 100644 --- a/frontend/widgets/VolumeAccessibleInterface.hpp +++ b/frontend/widgets/VolumeAccessibleInterface.hpp @@ -1,324 +1,9 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include #include -#include "absolute-slider.hpp" -class QPushButton; -class VolumeMeterTimer; class VolumeSlider; -class VolumeMeter : public QWidget { - Q_OBJECT - Q_PROPERTY(QColor backgroundNominalColor READ getBackgroundNominalColor WRITE setBackgroundNominalColor - DESIGNABLE true) - Q_PROPERTY(QColor backgroundWarningColor READ getBackgroundWarningColor WRITE setBackgroundWarningColor - DESIGNABLE true) - Q_PROPERTY( - QColor backgroundErrorColor READ getBackgroundErrorColor WRITE setBackgroundErrorColor DESIGNABLE true) - Q_PROPERTY(QColor foregroundNominalColor READ getForegroundNominalColor WRITE setForegroundNominalColor - DESIGNABLE true) - Q_PROPERTY(QColor foregroundWarningColor READ getForegroundWarningColor WRITE setForegroundWarningColor - DESIGNABLE true) - Q_PROPERTY( - QColor foregroundErrorColor READ getForegroundErrorColor WRITE setForegroundErrorColor DESIGNABLE true) - - Q_PROPERTY(QColor backgroundNominalColorDisabled READ getBackgroundNominalColorDisabled WRITE - setBackgroundNominalColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor backgroundWarningColorDisabled READ getBackgroundWarningColorDisabled WRITE - setBackgroundWarningColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor backgroundErrorColorDisabled READ getBackgroundErrorColorDisabled WRITE - setBackgroundErrorColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundNominalColorDisabled READ getForegroundNominalColorDisabled WRITE - setForegroundNominalColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundWarningColorDisabled READ getForegroundWarningColorDisabled WRITE - setForegroundWarningColorDisabled DESIGNABLE true) - Q_PROPERTY(QColor foregroundErrorColorDisabled READ getForegroundErrorColorDisabled WRITE - setForegroundErrorColorDisabled DESIGNABLE true) - - Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor DESIGNABLE true) - Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE setMagnitudeColor DESIGNABLE true) - Q_PROPERTY(QColor majorTickColor READ getMajorTickColor WRITE setMajorTickColor DESIGNABLE true) - Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE setMinorTickColor DESIGNABLE true) - Q_PROPERTY(int meterThickness READ getMeterThickness WRITE setMeterThickness DESIGNABLE true) - Q_PROPERTY(qreal meterFontScaling READ getMeterFontScaling WRITE setMeterFontScaling DESIGNABLE true) - - // Levels are denoted in dBFS. - Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel DESIGNABLE true) - Q_PROPERTY(qreal warningLevel READ getWarningLevel WRITE setWarningLevel DESIGNABLE true) - Q_PROPERTY(qreal errorLevel READ getErrorLevel WRITE setErrorLevel DESIGNABLE true) - Q_PROPERTY(qreal clipLevel READ getClipLevel WRITE setClipLevel DESIGNABLE true) - Q_PROPERTY(qreal minimumInputLevel READ getMinimumInputLevel WRITE setMinimumInputLevel DESIGNABLE true) - - // Rates are denoted in dB/second. - Q_PROPERTY(qreal peakDecayRate READ getPeakDecayRate WRITE setPeakDecayRate DESIGNABLE true) - - // Time in seconds for the VU meter to integrate over. - Q_PROPERTY(qreal magnitudeIntegrationTime READ getMagnitudeIntegrationTime WRITE setMagnitudeIntegrationTime - DESIGNABLE true) - - // Duration is denoted in seconds. - Q_PROPERTY(qreal peakHoldDuration READ getPeakHoldDuration WRITE setPeakHoldDuration DESIGNABLE true) - Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration WRITE setInputPeakHoldDuration - DESIGNABLE true) - - friend class VolControl; - -private: - obs_volmeter_t *obs_volmeter; - static std::weak_ptr updateTimer; - std::shared_ptr updateTimerRef; - - inline void resetLevels(); - inline void doLayout(); - inline bool detectIdle(uint64_t ts); - inline void calculateBallistics(uint64_t ts, qreal timeSinceLastRedraw = 0.0); - inline void calculateBallisticsForChannel(int channelNr, uint64_t ts, qreal timeSinceLastRedraw); - - inline int convertToInt(float number); - void paintInputMeter(QPainter &painter, int x, int y, int width, int height, float peakHold); - void paintHMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold); - void paintHTicks(QPainter &painter, int x, int y, int width); - void paintVMeter(QPainter &painter, int x, int y, int width, int height, float magnitude, float peak, - float peakHold); - void paintVTicks(QPainter &painter, int x, int y, int height); - - QMutex dataMutex; - - bool recalculateLayout = true; - uint64_t currentLastUpdateTime = 0; - float currentMagnitude[MAX_AUDIO_CHANNELS]; - float currentPeak[MAX_AUDIO_CHANNELS]; - float currentInputPeak[MAX_AUDIO_CHANNELS]; - - int displayNrAudioChannels = 0; - float displayMagnitude[MAX_AUDIO_CHANNELS]; - float displayPeak[MAX_AUDIO_CHANNELS]; - float displayPeakHold[MAX_AUDIO_CHANNELS]; - uint64_t displayPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; - float displayInputPeakHold[MAX_AUDIO_CHANNELS]; - uint64_t displayInputPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; - - QFont tickFont; - QColor backgroundNominalColor; - QColor backgroundWarningColor; - QColor backgroundErrorColor; - QColor foregroundNominalColor; - QColor foregroundWarningColor; - QColor foregroundErrorColor; - - QColor backgroundNominalColorDisabled; - QColor backgroundWarningColorDisabled; - QColor backgroundErrorColorDisabled; - QColor foregroundNominalColorDisabled; - QColor foregroundWarningColorDisabled; - QColor foregroundErrorColorDisabled; - - QColor clipColor; - QColor magnitudeColor; - QColor majorTickColor; - QColor minorTickColor; - - int meterThickness; - qreal meterFontScaling; - - qreal minimumLevel; - qreal warningLevel; - qreal errorLevel; - qreal clipLevel; - qreal minimumInputLevel; - qreal peakDecayRate; - qreal magnitudeIntegrationTime; - qreal peakHoldDuration; - qreal inputPeakHoldDuration; - - QColor p_backgroundNominalColor; - QColor p_backgroundWarningColor; - QColor p_backgroundErrorColor; - QColor p_foregroundNominalColor; - QColor p_foregroundWarningColor; - QColor p_foregroundErrorColor; - - uint64_t lastRedrawTime = 0; - int channels = 0; - bool clipping = false; - bool vertical; - bool muted = false; - -public: - explicit VolumeMeter(QWidget *parent = nullptr, obs_volmeter_t *obs_volmeter = nullptr, bool vertical = false); - ~VolumeMeter(); - - void setLevels(const float magnitude[MAX_AUDIO_CHANNELS], const float peak[MAX_AUDIO_CHANNELS], - const float inputPeak[MAX_AUDIO_CHANNELS]); - QRect getBarRect() const; - bool needLayoutChange(); - - QColor getBackgroundNominalColor() const; - void setBackgroundNominalColor(QColor c); - QColor getBackgroundWarningColor() const; - void setBackgroundWarningColor(QColor c); - QColor getBackgroundErrorColor() const; - void setBackgroundErrorColor(QColor c); - QColor getForegroundNominalColor() const; - void setForegroundNominalColor(QColor c); - QColor getForegroundWarningColor() const; - void setForegroundWarningColor(QColor c); - QColor getForegroundErrorColor() const; - void setForegroundErrorColor(QColor c); - - QColor getBackgroundNominalColorDisabled() const; - void setBackgroundNominalColorDisabled(QColor c); - QColor getBackgroundWarningColorDisabled() const; - void setBackgroundWarningColorDisabled(QColor c); - QColor getBackgroundErrorColorDisabled() const; - void setBackgroundErrorColorDisabled(QColor c); - QColor getForegroundNominalColorDisabled() const; - void setForegroundNominalColorDisabled(QColor c); - QColor getForegroundWarningColorDisabled() const; - void setForegroundWarningColorDisabled(QColor c); - QColor getForegroundErrorColorDisabled() const; - void setForegroundErrorColorDisabled(QColor c); - - QColor getClipColor() const; - void setClipColor(QColor c); - QColor getMagnitudeColor() const; - void setMagnitudeColor(QColor c); - QColor getMajorTickColor() const; - void setMajorTickColor(QColor c); - QColor getMinorTickColor() const; - void setMinorTickColor(QColor c); - int getMeterThickness() const; - void setMeterThickness(int v); - qreal getMeterFontScaling() const; - void setMeterFontScaling(qreal v); - qreal getMinimumLevel() const; - void setMinimumLevel(qreal v); - qreal getWarningLevel() const; - void setWarningLevel(qreal v); - qreal getErrorLevel() const; - void setErrorLevel(qreal v); - qreal getClipLevel() const; - void setClipLevel(qreal v); - qreal getMinimumInputLevel() const; - void setMinimumInputLevel(qreal v); - qreal getPeakDecayRate() const; - void setPeakDecayRate(qreal v); - qreal getMagnitudeIntegrationTime() const; - void setMagnitudeIntegrationTime(qreal v); - qreal getPeakHoldDuration() const; - void setPeakHoldDuration(qreal v); - qreal getInputPeakHoldDuration() const; - void setInputPeakHoldDuration(qreal v); - void setPeakMeterType(enum obs_peak_meter_type peakMeterType); - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void wheelEvent(QWheelEvent *event) override; - -protected: - void paintEvent(QPaintEvent *event) override; - void changeEvent(QEvent *e) override; -}; - -class VolumeMeterTimer : public QTimer { - Q_OBJECT - -public: - inline VolumeMeterTimer() : QTimer() {} - - void AddVolControl(VolumeMeter *meter); - void RemoveVolControl(VolumeMeter *meter); - -protected: - void timerEvent(QTimerEvent *event) override; - QList volumeMeters; -}; - -class QLabel; -class VolumeSlider; -class MuteCheckBox; -class OBSSourceLabel; - -class VolControl : public QFrame { - Q_OBJECT - -private: - OBSSource source; - std::vector sigs; - OBSSourceLabel *nameLabel; - QLabel *volLabel; - VolumeMeter *volMeter; - VolumeSlider *slider; - MuteCheckBox *mute; - QPushButton *config = nullptr; - float levelTotal; - float levelCount; - OBSFader obs_fader; - OBSVolMeter obs_volmeter; - bool vertical; - QMenu *contextMenu; - - static void OBSVolumeChanged(void *param, float db); - static void OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], - const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]); - static void OBSVolumeMuted(void *data, calldata_t *calldata); - static void OBSMixersOrMonitoringChanged(void *data, calldata_t *); - - void EmitConfigClicked(); - -private slots: - void VolumeChanged(); - void VolumeMuted(bool muted); - void MixersOrMonitoringChanged(); - - void SetMuted(bool checked); - void SliderChanged(int vol); - void updateText(); - -signals: - void ConfigClicked(); - -public: - explicit VolControl(OBSSource source, bool showConfig = false, bool vertical = false); - ~VolControl(); - - inline obs_source_t *GetSource() const { return source; } - - void SetMeterDecayRate(qreal q); - void setPeakMeterType(enum obs_peak_meter_type peakMeterType); - - void EnableSlider(bool enable); - inline void SetContextMenu(QMenu *cm) { contextMenu = cm; } - - void refreshColors(); -}; - -class VolumeSlider : public AbsoluteSlider { - Q_OBJECT - -public: - obs_fader_t *fad; - - VolumeSlider(obs_fader_t *fader, QWidget *parent = nullptr); - VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, QWidget *parent = nullptr); - - bool getDisplayTicks() const; - void setDisplayTicks(bool display); - -private: - bool displayTicks = false; - QColor tickColor; - -protected: - virtual void paintEvent(QPaintEvent *event) override; -}; - class VolumeAccessibleInterface : public QAccessibleWidget { public: diff --git a/frontend/widgets/VolumeMeter.cpp b/frontend/widgets/VolumeMeter.cpp index cd00c14d7..5379a9e8d 100644 --- a/frontend/widgets/VolumeMeter.cpp +++ b/frontend/widgets/VolumeMeter.cpp @@ -1,21 +1,13 @@ -#include "window-basic-main.hpp" -#include "moc_volume-control.cpp" -#include "obs-app.hpp" -#include "mute-checkbox.hpp" -#include "absolute-slider.hpp" -#include "source-label.hpp" +#include "VolumeMeter.hpp" -#include -#include -#include -#include -#include -#include +#include +#include + +#include +#include #include -using namespace std; - -#define FADER_PRECISION 4096.0 +#include "moc_VolumeMeter.cpp" // Size of the audio indicator in pixels #define INDICATOR_THICKNESS 3 @@ -25,385 +17,6 @@ using namespace std; std::weak_ptr VolumeMeter::updateTimer; -static inline Qt::CheckState GetCheckState(bool muted, bool unassigned) -{ - if (muted) - return Qt::Checked; - else if (unassigned) - return Qt::PartiallyChecked; - else - return Qt::Unchecked; -} - -static inline bool IsSourceUnassigned(obs_source_t *source) -{ - uint32_t mixes = (obs_source_get_audio_mixers(source) & ((1 << MAX_AUDIO_MIXES) - 1)); - obs_monitoring_type mt = obs_source_get_monitoring_type(source); - - return mixes == 0 && mt != OBS_MONITORING_TYPE_MONITOR_ONLY; -} - -static void ShowUnassignedWarning(const char *name) -{ - auto msgBox = [=]() { - QMessageBox msgbox(App()->GetMainWindow()); - msgbox.setWindowTitle(QTStr("VolControl.UnassignedWarning.Title")); - msgbox.setText(QTStr("VolControl.UnassignedWarning.Text").arg(name)); - msgbox.setIcon(QMessageBox::Icon::Information); - msgbox.addButton(QMessageBox::Ok); - - QCheckBox *cb = new QCheckBox(QTStr("DoNotShowAgain")); - msgbox.setCheckBox(cb); - - msgbox.exec(); - - if (cb->isChecked()) { - config_set_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources", true); - config_save_safe(App()->GetUserConfig(), "tmp", nullptr); - } - }; - - QMetaObject::invokeMethod(App(), "Exec", Qt::QueuedConnection, Q_ARG(VoidFunc, msgBox)); -} - -void VolControl::OBSVolumeChanged(void *data, float db) -{ - Q_UNUSED(db); - VolControl *volControl = static_cast(data); - - QMetaObject::invokeMethod(volControl, "VolumeChanged"); -} - -void VolControl::OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], - const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]) -{ - VolControl *volControl = static_cast(data); - - volControl->volMeter->setLevels(magnitude, peak, inputPeak); -} - -void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata) -{ - VolControl *volControl = static_cast(data); - bool muted = calldata_bool(calldata, "muted"); - - QMetaObject::invokeMethod(volControl, "VolumeMuted", Q_ARG(bool, muted)); -} - -void VolControl::VolumeChanged() -{ - slider->blockSignals(true); - slider->setValue((int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION)); - slider->blockSignals(false); - - updateText(); -} - -void VolControl::VolumeMuted(bool muted) -{ - bool unassigned = IsSourceUnassigned(source); - - auto newState = GetCheckState(muted, unassigned); - if (mute->checkState() != newState) - mute->setCheckState(newState); - - volMeter->muted = muted || unassigned; -} - -void VolControl::OBSMixersOrMonitoringChanged(void *data, calldata_t *) -{ - - VolControl *volControl = static_cast(data); - QMetaObject::invokeMethod(volControl, "MixersOrMonitoringChanged", Qt::QueuedConnection); -} - -void VolControl::MixersOrMonitoringChanged() -{ - bool muted = obs_source_muted(source); - bool unassigned = IsSourceUnassigned(source); - - auto newState = GetCheckState(muted, unassigned); - if (mute->checkState() != newState) - mute->setCheckState(newState); - - volMeter->muted = muted || unassigned; -} - -void VolControl::SetMuted(bool) -{ - bool checked = mute->checkState() == Qt::Checked; - bool prev = obs_source_muted(source); - obs_source_set_muted(source, checked); - bool unassigned = IsSourceUnassigned(source); - - if (!checked && unassigned) { - mute->setCheckState(Qt::PartiallyChecked); - /* Show notice about the source no being assigned to any tracks */ - bool has_shown_warning = - config_get_bool(App()->GetUserConfig(), "General", "WarnedAboutUnassignedSources"); - if (!has_shown_warning) - ShowUnassignedWarning(obs_source_get_name(source)); - } - - auto undo_redo = [](const std::string &uuid, bool val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_muted(source, val); - }; - - QString text = QTStr(checked ? "Undo.Volume.Mute" : "Undo.Volume.Unmute"); - - const char *name = obs_source_get_name(source); - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(text.arg(name), std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, checked), uuid, uuid); -} - -void VolControl::SliderChanged(int vol) -{ - float prev = obs_source_get_volume(source); - - obs_fader_set_deflection(obs_fader, float(vol) / FADER_PRECISION); - updateText(); - - auto undo_redo = [](const std::string &uuid, float val) { - OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str()); - obs_source_set_volume(source, val); - }; - - float val = obs_source_get_volume(source); - const char *name = obs_source_get_name(source); - const char *uuid = obs_source_get_uuid(source); - OBSBasic::Get()->undo_s.add_action(QTStr("Undo.Volume.Change").arg(name), - std::bind(undo_redo, std::placeholders::_1, prev), - std::bind(undo_redo, std::placeholders::_1, val), uuid, uuid, true); -} - -void VolControl::updateText() -{ - QString text; - float db = obs_fader_get_db(obs_fader); - - if (db < -96.0f) - text = "-inf dB"; - else - text = QString::number(db, 'f', 1).append(" dB"); - - volLabel->setText(text); - - bool muted = obs_source_muted(source); - const char *accTextLookup = muted ? "VolControl.SliderMuted" : "VolControl.SliderUnmuted"; - - QString sourceName = obs_source_get_name(source); - QString accText = QTStr(accTextLookup).arg(sourceName); - - slider->setAccessibleName(accText); -} - -void VolControl::EmitConfigClicked() -{ - emit ConfigClicked(); -} - -void VolControl::SetMeterDecayRate(qreal q) -{ - volMeter->setPeakDecayRate(q); -} - -void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType) -{ - volMeter->setPeakMeterType(peakMeterType); -} - -VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical) - : source(std::move(source_)), - levelTotal(0.0f), - levelCount(0.0f), - obs_fader(obs_fader_create(OBS_FADER_LOG)), - obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)), - vertical(vertical), - contextMenu(nullptr) -{ - nameLabel = new OBSSourceLabel(source); - volLabel = new QLabel(); - mute = new MuteCheckBox(); - - volLabel->setObjectName("volLabel"); - volLabel->setAlignment(Qt::AlignCenter); - -#ifdef __APPLE__ - mute->setAttribute(Qt::WA_LayoutUsesWidgetRect); -#endif - - QString sourceName = obs_source_get_name(source); - setObjectName(sourceName); - - if (showConfig) { - config = new QPushButton(this); - config->setProperty("class", "icon-dots-vert"); - config->setAutoDefault(false); - - config->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - - config->setAccessibleName(QTStr("VolControl.Properties").arg(sourceName)); - - connect(config, &QAbstractButton::clicked, this, &VolControl::EmitConfigClicked); - } - - QVBoxLayout *mainLayout = new QVBoxLayout; - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(0); - - if (vertical) { - QHBoxLayout *nameLayout = new QHBoxLayout; - QHBoxLayout *controlLayout = new QHBoxLayout; - QHBoxLayout *volLayout = new QHBoxLayout; - QFrame *meterFrame = new QFrame; - QHBoxLayout *meterLayout = new QHBoxLayout; - - volMeter = new VolumeMeter(nullptr, obs_volmeter, true); - slider = new VolumeSlider(obs_fader, Qt::Vertical); - slider->setLayoutDirection(Qt::LeftToRight); - slider->setDisplayTicks(true); - - nameLayout->setAlignment(Qt::AlignCenter); - meterLayout->setAlignment(Qt::AlignCenter); - controlLayout->setAlignment(Qt::AlignCenter); - volLayout->setAlignment(Qt::AlignCenter); - - meterFrame->setObjectName("volMeterFrame"); - - nameLayout->setContentsMargins(0, 0, 0, 0); - nameLayout->setSpacing(0); - nameLayout->addWidget(nameLabel); - - controlLayout->setContentsMargins(0, 0, 0, 0); - controlLayout->setSpacing(0); - - // Add Headphone (audio monitoring) widget here - controlLayout->addWidget(mute); - - if (showConfig) { - controlLayout->addWidget(config); - } - - meterLayout->setContentsMargins(0, 0, 0, 0); - meterLayout->setSpacing(0); - meterLayout->addWidget(slider); - meterLayout->addWidget(volMeter); - - meterFrame->setLayout(meterLayout); - - volLayout->setContentsMargins(0, 0, 0, 0); - volLayout->setSpacing(0); - volLayout->addWidget(volLabel); - volLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum)); - - mainLayout->addItem(nameLayout); - mainLayout->addItem(volLayout); - mainLayout->addWidget(meterFrame); - mainLayout->addItem(controlLayout); - - volMeter->setFocusProxy(slider); - - // Default size can cause clipping of long names in vertical layout. - QFont font = nameLabel->font(); - QFontInfo info(font); - nameLabel->setFont(font); - - setMaximumWidth(110); - } else { - QHBoxLayout *textLayout = new QHBoxLayout; - QHBoxLayout *controlLayout = new QHBoxLayout; - QFrame *meterFrame = new QFrame; - QVBoxLayout *meterLayout = new QVBoxLayout; - QVBoxLayout *buttonLayout = new QVBoxLayout; - - volMeter = new VolumeMeter(nullptr, obs_volmeter, false); - volMeter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - - slider = new VolumeSlider(obs_fader, Qt::Horizontal); - slider->setLayoutDirection(Qt::LeftToRight); - slider->setDisplayTicks(true); - - textLayout->setContentsMargins(0, 0, 0, 0); - textLayout->addWidget(nameLabel); - textLayout->addWidget(volLabel); - textLayout->setAlignment(nameLabel, Qt::AlignLeft); - textLayout->setAlignment(volLabel, Qt::AlignRight); - - meterFrame->setObjectName("volMeterFrame"); - meterFrame->setLayout(meterLayout); - - meterLayout->setContentsMargins(0, 0, 0, 0); - meterLayout->setSpacing(0); - - meterLayout->addWidget(volMeter); - meterLayout->addWidget(slider); - - buttonLayout->setContentsMargins(0, 0, 0, 0); - buttonLayout->setSpacing(0); - - if (showConfig) { - buttonLayout->addWidget(config); - } - buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding)); - buttonLayout->addWidget(mute); - - controlLayout->addItem(buttonLayout); - controlLayout->addWidget(meterFrame); - - mainLayout->addItem(textLayout); - mainLayout->addItem(controlLayout); - - volMeter->setFocusProxy(slider); - } - - setLayout(mainLayout); - - nameLabel->setText(sourceName); - - slider->setMinimum(0); - slider->setMaximum(int(FADER_PRECISION)); - - bool muted = obs_source_muted(source); - bool unassigned = IsSourceUnassigned(source); - mute->setCheckState(GetCheckState(muted, unassigned)); - volMeter->muted = muted || unassigned; - mute->setAccessibleName(QTStr("VolControl.Mute").arg(sourceName)); - obs_fader_add_callback(obs_fader, OBSVolumeChanged, this); - obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this); - - sigs.emplace_back(obs_source_get_signal_handler(source), "mute", OBSVolumeMuted, this); - sigs.emplace_back(obs_source_get_signal_handler(source), "audio_mixers", OBSMixersOrMonitoringChanged, this); - sigs.emplace_back(obs_source_get_signal_handler(source), "audio_monitoring", OBSMixersOrMonitoringChanged, - this); - - QWidget::connect(slider, &VolumeSlider::valueChanged, this, &VolControl::SliderChanged); - QWidget::connect(mute, &MuteCheckBox::clicked, this, &VolControl::SetMuted); - - obs_fader_attach_source(obs_fader, source); - obs_volmeter_attach_source(obs_volmeter, source); - - /* Call volume changed once to init the slider position and label */ - VolumeChanged(); -} - -void VolControl::EnableSlider(bool enable) -{ - slider->setEnabled(enable); -} - -VolControl::~VolControl() -{ - obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this); - obs_volmeter_remove_callback(obs_volmeter, OBSVolumeLevel, this); - - sigs.clear(); - - if (contextMenu) - contextMenu->close(); -} - static inline QColor color_from_int(long long val) { QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff, (val >> 24) & 0xff); @@ -636,16 +249,6 @@ void VolumeMeter::setMeterFontScaling(qreal v) recalculateLayout = true; } -void VolControl::refreshColors() -{ - volMeter->setBackgroundNominalColor(volMeter->getBackgroundNominalColor()); - volMeter->setBackgroundWarningColor(volMeter->getBackgroundWarningColor()); - volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor()); - volMeter->setForegroundNominalColor(volMeter->getForegroundNominalColor()); - volMeter->setForegroundWarningColor(volMeter->getForegroundWarningColor()); - volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor()); -} - qreal VolumeMeter::getMinimumLevel() const { return minimumLevel; @@ -1349,159 +952,3 @@ void VolumeMeter::changeEvent(QEvent *e) QWidget::changeEvent(e); } - -void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) -{ - volumeMeters.push_back(meter); -} - -void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter) -{ - volumeMeters.removeOne(meter); -} - -void VolumeMeterTimer::timerEvent(QTimerEvent *) -{ - for (VolumeMeter *meter : volumeMeters) { - if (meter->needLayoutChange()) { - // Tell paintEvent to update layout and paint everything - meter->update(); - } else { - // Tell paintEvent to paint only the bars - meter->update(meter->getBarRect()); - } - } -} - -VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent) : AbsoluteSlider(parent) -{ - fad = fader; -} - -VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, QWidget *parent) - : AbsoluteSlider(orientation, parent) -{ - fad = fader; -} - -bool VolumeSlider::getDisplayTicks() const -{ - return displayTicks; -} - -void VolumeSlider::setDisplayTicks(bool display) -{ - displayTicks = display; -} - -void VolumeSlider::paintEvent(QPaintEvent *event) -{ - if (!getDisplayTicks()) { - QSlider::paintEvent(event); - return; - } - - QPainter painter(this); - QColor tickColor(91, 98, 115, 255); - - obs_fader_conversion_t fader_db_to_def = obs_fader_db_to_def(fad); - - QStyleOptionSlider opt; - initStyleOption(&opt); - - QRect groove = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, this); - QRect handle = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this); - - if (orientation() == Qt::Horizontal) { - const int sliderWidth = groove.width() - handle.width(); - - float tickLength = groove.height() * 1.5; - tickLength = std::max((int)tickLength + groove.height(), 8 + groove.height()); - - float yPos = groove.center().y() - (tickLength / 2) + 1; - - for (int db = -10; db >= -90; db -= 10) { - float tickValue = fader_db_to_def(db); - - float xPos = groove.left() + (tickValue * sliderWidth) + (handle.width() / 2); - painter.fillRect(xPos, yPos, 1, tickLength, tickColor); - } - } - - if (orientation() == Qt::Vertical) { - const int sliderHeight = groove.height() - handle.height(); - - float tickLength = groove.width() * 1.5; - tickLength = std::max((int)tickLength + groove.width(), 8 + groove.width()); - - float xPos = groove.center().x() - (tickLength / 2) + 1; - - for (int db = -10; db >= -96; db -= 10) { - float tickValue = fader_db_to_def(db); - - float yPos = - groove.height() + groove.top() - (tickValue * sliderHeight) - (handle.height() / 2); - painter.fillRect(xPos, yPos, tickLength, 1, tickColor); - } - } - - QSlider::paintEvent(event); -} - -VolumeAccessibleInterface::VolumeAccessibleInterface(QWidget *w) : QAccessibleWidget(w) {} - -VolumeSlider *VolumeAccessibleInterface::slider() const -{ - return qobject_cast(object()); -} - -QString VolumeAccessibleInterface::text(QAccessible::Text t) const -{ - if (slider()->isVisible()) { - switch (t) { - case QAccessible::Text::Value: - return currentValue().toString(); - default: - break; - } - } - return QAccessibleWidget::text(t); -} - -QVariant VolumeAccessibleInterface::currentValue() const -{ - QString text; - float db = obs_fader_get_db(slider()->fad); - - if (db < -96.0f) - text = "-inf dB"; - else - text = QString::number(db, 'f', 1).append(" dB"); - - return text; -} - -void VolumeAccessibleInterface::setCurrentValue(const QVariant &value) -{ - slider()->setValue(value.toInt()); -} - -QVariant VolumeAccessibleInterface::maximumValue() const -{ - return slider()->maximum(); -} - -QVariant VolumeAccessibleInterface::minimumValue() const -{ - return slider()->minimum(); -} - -QVariant VolumeAccessibleInterface::minimumStepSize() const -{ - return slider()->singleStep(); -} - -QAccessible::Role VolumeAccessibleInterface::role() const -{ - return QAccessible::Role::Slider; -} diff --git a/frontend/widgets/VolumeMeter.hpp b/frontend/widgets/VolumeMeter.hpp index 51c77f247..a8cbc3d45 100644 --- a/frontend/widgets/VolumeMeter.hpp +++ b/frontend/widgets/VolumeMeter.hpp @@ -1,18 +1,13 @@ #pragma once #include -#include -#include -#include -#include -#include -#include -#include -#include "absolute-slider.hpp" -class QPushButton; +#include +#include + +#define FADER_PRECISION 4096.0 + class VolumeMeterTimer; -class VolumeSlider; class VolumeMeter : public QWidget { Q_OBJECT @@ -225,117 +220,3 @@ protected: void paintEvent(QPaintEvent *event) override; void changeEvent(QEvent *e) override; }; - -class VolumeMeterTimer : public QTimer { - Q_OBJECT - -public: - inline VolumeMeterTimer() : QTimer() {} - - void AddVolControl(VolumeMeter *meter); - void RemoveVolControl(VolumeMeter *meter); - -protected: - void timerEvent(QTimerEvent *event) override; - QList volumeMeters; -}; - -class QLabel; -class VolumeSlider; -class MuteCheckBox; -class OBSSourceLabel; - -class VolControl : public QFrame { - Q_OBJECT - -private: - OBSSource source; - std::vector sigs; - OBSSourceLabel *nameLabel; - QLabel *volLabel; - VolumeMeter *volMeter; - VolumeSlider *slider; - MuteCheckBox *mute; - QPushButton *config = nullptr; - float levelTotal; - float levelCount; - OBSFader obs_fader; - OBSVolMeter obs_volmeter; - bool vertical; - QMenu *contextMenu; - - static void OBSVolumeChanged(void *param, float db); - static void OBSVolumeLevel(void *data, const float magnitude[MAX_AUDIO_CHANNELS], - const float peak[MAX_AUDIO_CHANNELS], const float inputPeak[MAX_AUDIO_CHANNELS]); - static void OBSVolumeMuted(void *data, calldata_t *calldata); - static void OBSMixersOrMonitoringChanged(void *data, calldata_t *); - - void EmitConfigClicked(); - -private slots: - void VolumeChanged(); - void VolumeMuted(bool muted); - void MixersOrMonitoringChanged(); - - void SetMuted(bool checked); - void SliderChanged(int vol); - void updateText(); - -signals: - void ConfigClicked(); - -public: - explicit VolControl(OBSSource source, bool showConfig = false, bool vertical = false); - ~VolControl(); - - inline obs_source_t *GetSource() const { return source; } - - void SetMeterDecayRate(qreal q); - void setPeakMeterType(enum obs_peak_meter_type peakMeterType); - - void EnableSlider(bool enable); - inline void SetContextMenu(QMenu *cm) { contextMenu = cm; } - - void refreshColors(); -}; - -class VolumeSlider : public AbsoluteSlider { - Q_OBJECT - -public: - obs_fader_t *fad; - - VolumeSlider(obs_fader_t *fader, QWidget *parent = nullptr); - VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation, QWidget *parent = nullptr); - - bool getDisplayTicks() const; - void setDisplayTicks(bool display); - -private: - bool displayTicks = false; - QColor tickColor; - -protected: - virtual void paintEvent(QPaintEvent *event) override; -}; - -class VolumeAccessibleInterface : public QAccessibleWidget { - -public: - VolumeAccessibleInterface(QWidget *w); - - QVariant currentValue() const; - void setCurrentValue(const QVariant &value); - - QVariant maximumValue() const; - QVariant minimumValue() const; - - QVariant minimumStepSize() const; - -private: - VolumeSlider *slider() const; - -protected: - virtual QAccessible::Role role() const override; - virtual QString text(QAccessible::Text t) const override; -};