#include "config-dialog.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hotkey-edit.hpp" #include "obs-module.h" #include "version.h" #include "vertical-canvas.hpp" #include #include OBSBasicSettings::OBSBasicSettings(CanvasDock *canvas_dock, QMainWindow *parent) : QDialog(parent), canvasDock(canvas_dock) { setMinimumWidth(983); setMinimumHeight(480); listWidget = new QListWidget(this); listWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Expanding); listWidget->setMaximumWidth(180); QListWidgetItem *listwidgetitem = new QListWidgetItem(listWidget); listwidgetitem->setIcon(QIcon(QString::fromUtf8(":/settings/images/settings/general.svg"))); listwidgetitem->setText(QString::fromUtf8(obs_module_text("General"))); listwidgetitem = new QListWidgetItem(listWidget); listwidgetitem->setIcon(QIcon(QString::fromUtf8(":/settings/images/settings/stream.svg"))); listwidgetitem->setText(QString::fromUtf8(obs_module_text("Streaming"))); listwidgetitem = new QListWidgetItem(listWidget); listwidgetitem->setIcon(QIcon(QString::fromUtf8(":/settings/images/settings/output.svg"))); listwidgetitem->setText(QString::fromUtf8(obs_module_text("Recording"))); listwidgetitem = new QListWidgetItem(listWidget); listwidgetitem->setIcon(canvasDock->GetIconFromType(OBS_ICON_TYPE_UNKNOWN)); listwidgetitem->setText(QString::fromUtf8(obs_module_text("Help"))); listwidgetitem = new QListWidgetItem(listWidget); listwidgetitem->setIcon(QIcon(QString::fromUtf8(":/vulcast/media/logo.png"))); listwidgetitem->setText(QString::fromUtf8(obs_module_text("SupportButton"))); listWidget->setCurrentRow(0); listWidget->setSpacing(1); auto settingsPages = new QStackedWidget; settingsPages->setContentsMargins(0, 0, 0, 0); settingsPages->setFrameShape(QFrame::NoFrame); settingsPages->setLineWidth(0); QWidget *generalPage = new QWidget; QScrollArea *scrollArea = new QScrollArea; scrollArea->setWidget(generalPage); scrollArea->setWidgetResizable(true); scrollArea->setLineWidth(0); scrollArea->setFrameShape(QFrame::NoFrame); settingsPages->addWidget(scrollArea); auto streamingPage = new QWidget; scrollArea = new QScrollArea; scrollArea->setWidget(streamingPage); scrollArea->setWidgetResizable(true); scrollArea->setLineWidth(0); scrollArea->setFrameShape(QFrame::NoFrame); settingsPages->addWidget(scrollArea); auto recordingPage = new QWidget; scrollArea = new QScrollArea; scrollArea->setWidget(recordingPage); scrollArea->setWidgetResizable(true); scrollArea->setLineWidth(0); scrollArea->setFrameShape(QFrame::NoFrame); settingsPages->addWidget(scrollArea); auto helpPage = new QWidget; scrollArea = new QScrollArea; scrollArea->setWidget(helpPage); scrollArea->setWidgetResizable(true); scrollArea->setLineWidth(0); scrollArea->setFrameShape(QFrame::NoFrame); settingsPages->addWidget(scrollArea); // Support page QWidget *supportPage = new QWidget; auto supportPageLayout = new QVBoxLayout; supportPage->setLayout(supportPageLayout); auto supportInfoBox = new QGroupBox(QString::fromUtf8(obs_module_text("SupportTitle"))); supportInfoBox->setStyleSheet("padding-top: 12px"); auto supportLayout = new QVBoxLayout; supportInfoBox->setLayout(supportLayout); auto supportLabel = new QLabel(QString::fromUtf8(obs_module_text("SupportText"))); supportLabel->setStyleSheet("font-size: 14px"); supportLabel->setWordWrap(true); supportLabel->setTextFormat(Qt::RichText); supportLabel->setOpenExternalLinks(true); supportLayout->addWidget(supportLabel, 1); supportPageLayout->addWidget(supportInfoBox, 1, Qt::AlignTop); settingsPages->addWidget(supportPage); connect(listWidget, &QListWidget::currentRowChanged, settingsPages, &QStackedWidget::setCurrentIndex); auto generalGroup = new QGroupBox; generalGroup->setStyleSheet(QString("QGroupBox{ padding-top: 4px;}")); auto generalLayout = new QFormLayout; generalLayout->setContentsMargins(9, 2, 9, 9); generalLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); generalLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); generalGroup->setLayout(generalLayout); auto general_title_layout = new QHBoxLayout; auto general_title = new QLabel(QString::fromUtf8(obs_module_text("General"))); general_title->setStyleSheet(QString::fromUtf8("font-weight: bold;")); general_title_layout->addWidget(general_title, 0, Qt::AlignLeft); auto guide_link = new QLabel(QString::fromUtf8("") + QString::fromUtf8(obs_module_text("ViewGuide")) + QString::fromUtf8("")); guide_link->setOpenExternalLinks(true); general_title_layout->addWidget(guide_link, 0, Qt::AlignRight); generalLayout->addRow(general_title_layout); resolution = new QComboBox; resolution->setEditable(true); resolution->addItem("720x1280"); resolution->addItem("1080x1920"); resolution->addItem("1080x1350"); resolution->addItem("1280x720"); resolution->addItem("1920x1080"); resolution->addItem("2560x1440"); resolution->addItem("3840x2160"); generalLayout->addRow(QString::fromUtf8(obs_module_text("Resolution")), resolution); audioBitrate = new QComboBox; audioBitrate->addItem("64", QVariant(64)); audioBitrate->addItem("96", QVariant(96)); audioBitrate->addItem("128", QVariant(128)); audioBitrate->addItem("160", QVariant(160)); audioBitrate->addItem("192", QVariant(192)); audioBitrate->addItem("224", QVariant(224)); audioBitrate->addItem("256", QVariant(256)); audioBitrate->addItem("288", QVariant(288)); audioBitrate->addItem("320", QVariant(320)); audioBitrate->setCurrentText("160"); generalLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.AudioBitrate")), audioBitrate); virtualCameraMode = new QComboBox; virtualCameraMode->addItem(QString::fromUtf8(obs_module_text("VirtualCameraModeVertical")), QVariant(VIRTUAL_CAMERA_VERTICAL)); virtualCameraMode->addItem(QString::fromUtf8(obs_module_text("VirtualCameraModeMain")), QVariant(VIRTUAL_CAMERA_MAIN)); virtualCameraMode->addItem(QString::fromUtf8(obs_module_text("VirtualCameraModeBoth")), QVariant(VIRTUAL_CAMERA_BOTH)); generalLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.VCam.VirtualCamera")), virtualCameraMode); auto backtrackGroup = new QGroupBox; backtrackGroup->setStyleSheet(QString("QGroupBox{ padding-top: 4px;}")); auto backtrackLayout = new QFormLayout; backtrackLayout->setContentsMargins(9, 2, 9, 9); backtrackLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); backtrackLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); backtrackGroup->setLayout(backtrackLayout); auto backtrack_title_layout = new QHBoxLayout; auto backtrack_title = new QLabel(QString::fromUtf8(obs_module_text("Backtrack"))); backtrack_title->setStyleSheet(QString::fromUtf8("font-weight: bold;")); backtrack_title_layout->addWidget(backtrack_title, 0, Qt::AlignLeft); guide_link = new QLabel(QString::fromUtf8("") + QString::fromUtf8(obs_module_text("ViewGuide")) + QString::fromUtf8("")); guide_link->setOpenExternalLinks(true); backtrack_title_layout->addWidget(guide_link, 0, Qt::AlignRight); backtrackLayout->addRow(backtrack_title_layout); backtrackLayout->addRow(new QLabel(QString::fromUtf8(obs_module_text("BacktrackEncoder")))); backtrackClip = new QCheckBox(QString::fromUtf8(obs_module_text("BacktrackEnable"))); backtrackLayout->addWidget(backtrackClip); backtrackDuration = new QSpinBox; backtrackDuration->setSuffix(" s"); backtrackDuration->setMinimum(5); backtrackDuration->setMaximum(21600); backtrackLayout->addRow(QString::fromUtf8(obs_module_text("BacktrackDuration")), backtrackDuration); QLayout *backtrackPathLayout = new QHBoxLayout; backtrackPath = new QLineEdit(); backtrackPath->setReadOnly(true); auto button = new QPushButton(QString::fromUtf8(obs_frontend_get_locale_string("Browse"))); button->setProperty("themeID", "settingsButtons"); button->setProperty("class", "icon-gear"); button->setAutoDefault(false); connect(button, &QPushButton::clicked, [this] { const QString dir = QFileDialog::getExistingDirectory(this, QString::fromUtf8(obs_module_text("BacktrackPath")), backtrackPath->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir.isEmpty()) return; backtrackPath->setText(dir); }); backtrackPathLayout->addWidget(backtrackPath); backtrackPathLayout->addWidget(button); backtrackLayout->addRow(QString::fromUtf8(obs_module_text("BacktrackPath")), backtrackPathLayout); auto replayHotkeys = GetHotKeysFromOutput(canvasDock->replayOutput); for (auto &hotkey : replayHotkeys) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); if (strcmp(hn, "ReplayBuffer.Save") == 0) { backtrackLayout->addRow(QString::fromUtf8(obs_module_text("SaveBacktrackHotkey")), hw); } else { backtrackLayout->addRow(QString::fromUtf8(obs_hotkey_get_description(hotkey)), hw); } hotkeys.push_back(hw); } OBSHotkeyWidget *otherHotkey = nullptr; auto hotkey = GetHotkeyByName("VerticalCanvasDockStartBacktrack"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); otherHotkey = hw; auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_module_text("StartBacktrackHotkey"))); hw->label = label; backtrackLayout->addRow(label, hw); hotkeys.push_back(hw); } hotkey = GetHotkeyByName("VerticalCanvasDockStopBacktrack"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_module_text("StopBacktrackHotkey"))); hw->label = label; backtrackLayout->addRow(label, hw); hotkeys.push_back(hw); if (otherHotkey) { hw->label->pairPartner = otherHotkey->label; otherHotkey->label->pairPartner = hw->label; } } auto maxWidth = 180; for (int row = 0; row < generalLayout->rowCount(); row++) { auto item = generalLayout->itemAt(row, QFormLayout::LabelRole); if (!item) continue; auto label = dynamic_cast(item->widget()); if (!label) continue; label->setFixedWidth(maxWidth); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } for (int row = 0; row < backtrackLayout->rowCount(); row++) { auto item = backtrackLayout->itemAt(row, QFormLayout::LabelRole); if (!item) continue; auto label = dynamic_cast(item->widget()); if (!label) continue; label->setFixedWidth(maxWidth); label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } auto vb = new QVBoxLayout; vb->setContentsMargins(0, 0, 0, 0); vb->addWidget(generalGroup); vb->addWidget(backtrackGroup); vb->addStretch(); generalPage->setLayout(vb); auto streamingGroup = new QGroupBox; streamingGroup->setStyleSheet(QString("QGroupBox{ padding-top: 4px;}")); streamingGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); streamingLayout = new QFormLayout; streamingLayout->setContentsMargins(9, 2, 9, 9); streamingLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); streamingLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); auto streaming_title_layout = new QHBoxLayout; auto streaming_title = new QLabel(QString::fromUtf8(obs_module_text("Streaming"))); streaming_title->setStyleSheet(QString::fromUtf8("font-weight: bold;")); streaming_title_layout->addWidget(streaming_title, 0, Qt::AlignLeft); guide_link = new QLabel(QString::fromUtf8("") + QString::fromUtf8(obs_module_text("ViewGuide")) + QString::fromUtf8("")); guide_link->setOpenExternalLinks(true); streaming_title_layout->addWidget(guide_link, 0, Qt::AlignRight); streamingLayout->addRow(streaming_title_layout); if (!canvasDock->disable_stream_settings) { auto hl = new QHBoxLayout; auto addButton = new QPushButton(QIcon(QString::fromUtf8(":/res/images/plus.svg")), QString::fromUtf8(obs_frontend_get_locale_string("Add"))); addButton->setProperty("themeID", QVariant(QString::fromUtf8("addIconSmall"))); addButton->setProperty("class", "icon-plus"); connect(addButton, &QPushButton::clicked, [this] { AddServer(); }); hl->addWidget(addButton); auto removeButton = new QPushButton(QIcon(":/res/images/minus.svg"), QString::fromUtf8(obs_frontend_get_locale_string("Remove"))); removeButton->setProperty("themeID", QVariant(QString::fromUtf8("removeIconSmall"))); removeButton->setProperty("class", "icon-minus"); connect(removeButton, &QPushButton::clicked, [this] { if (servers.size() <= 1) return; auto idx = (int)servers.size(); streamingLayout->removeRow(idx); server_names.pop_back(); servers.pop_back(); keys.pop_back(); servers_enabled.pop_back(); }); hl->addWidget(removeButton); streamingLayout->addRow(hl); } streamingVideoBitrate = new QSpinBox; streamingVideoBitrate->setSuffix(" Kbps"); streamingVideoBitrate->setMinimum(2000); streamingVideoBitrate->setMaximum(1000000); streamingLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.VideoBitrate")), streamingVideoBitrate); streamingMatchMain = new QCheckBox(QString::fromUtf8(obs_module_text("StreamingMatchMain"))); streamingLayout->addWidget(streamingMatchMain); otherHotkey = nullptr; hotkey = GetHotkeyByName("VerticalCanvasDockStartStreaming"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); otherHotkey = hw; auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_module_text("StartStreamingHotkey"))); hw->label = label; streamingLayout->addRow(label, hw); hotkeys.push_back(hw); } hotkey = GetHotkeyByName("VerticalCanvasDockStopStreaming"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_module_text("StopStreamingHotkey"))); hw->label = label; streamingLayout->addRow(label, hw); hotkeys.push_back(hw); if (otherHotkey) { hw->label->pairPartner = otherHotkey->label; otherHotkey->label->pairPartner = hw->label; } } streamingGroup->setLayout(streamingLayout); auto streamingDelayGroup = new QGroupBox(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Advanced.StreamDelay"))); auto streamingDelayLayout = new QFormLayout; streamingDelayLayout->setContentsMargins(9, 2, 9, 9); streamingDelayLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); streamingDelayLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); streamDelayEnable = new QCheckBox(QString::fromUtf8(obs_frontend_get_locale_string("Enable"))); streamingDelayLayout->addWidget(streamDelayEnable); streamDelayDuration = new QSpinBox(); streamDelayDuration->setSuffix(" s"); streamDelayDuration->setMinimumSize(QSize(80, 0)); streamDelayDuration->setMinimum(1); streamDelayDuration->setMaximum(1800); streamDelayDuration->setEnabled(false); auto streamDelayDurationLabel = new QLabel(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Advanced.StreamDelay.Duration"))); streamDelayDurationLabel->setEnabled(false); streamingDelayLayout->addRow(streamDelayDurationLabel, streamDelayDuration); streamDelayPreserve = new QCheckBox(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Advanced.StreamDelay.Preserve"))); streamDelayPreserve->setEnabled(false); streamingDelayLayout->addWidget(streamDelayPreserve); QObject::connect(streamDelayEnable, &QCheckBox::toggled, streamDelayDurationLabel, &QLabel::setEnabled); QObject::connect(streamDelayEnable, &QCheckBox::toggled, streamDelayDuration, &QSpinBox::setEnabled); QObject::connect(streamDelayEnable, &QCheckBox::toggled, streamDelayPreserve, &QCheckBox::setEnabled); streamingDelayGroup->setLayout(streamingDelayLayout); auto streamingAdvancedGroup = new QGroupBox(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Advanced"))); auto streamingAdvancedLayout = new QFormLayout; streamingAdvancedLayout->setContentsMargins(9, 2, 9, 9); streamingAdvancedLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); streamingAdvancedLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); streamingUseMain = new QCheckBox(QString::fromUtf8(obs_module_text("UseMain"))); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(streamingUseMain, &QCheckBox::checkStateChanged, [this, streamingAdvancedLayout, streamingDelayGroup] { #else connect(streamingUseMain, &QCheckBox::stateChanged, [this, streamingAdvancedLayout, streamingDelayGroup] { #endif bool checked = streamingUseMain->isChecked(); streamingVideoBitrate->setEnabled(checked); streamingDelayGroup->setEnabled(!checked); for (int i = 1; i < streamingAdvancedLayout->rowCount(); i++) { auto field = streamingAdvancedLayout->itemAt(i, QFormLayout::FieldRole); if (field) { auto w = field->widget(); if (w) w->setEnabled(!checked); } auto label = streamingAdvancedLayout->itemAt(i, QFormLayout::LabelRole); if (label) { auto w = label->widget(); if (w) w->setEnabled(!checked); } } }); streamingAdvancedLayout->addWidget(streamingUseMain); auto audioTracks = new QFrame; audioTracks->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); audioTracks->setContentsMargins(0, 0, 0, 0); auto al = new QHBoxLayout; al->setContentsMargins(0, 0, 0, 0); audioTracks->setLayout(al); for (int i = 1; i <= MAX_AUDIO_MIXES; i++) { auto radio = new QRadioButton(QString::fromUtf8("%1").arg(i)); streamingAudioTracks.push_back(radio); al->addWidget(radio); } streamingAdvancedLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Adv.AudioTrack")), audioTracks); streamingEncoder = new QComboBox; const char *type; size_t idx = 0; while (obs_enum_encoder_types(idx++, &type)) { if (obs_get_encoder_type(type) != OBS_ENCODER_VIDEO) continue; uint32_t caps = obs_get_encoder_caps(type); if ((caps & (OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL)) != 0) continue; const char *codec = obs_get_encoder_codec(type); if (astrcmpi(codec, "h264") != 0 && astrcmpi(codec, "hevc") != 0 && astrcmpi(codec, "av1") != 0) continue; streamingEncoder->addItem(QString::fromUtf8(obs_encoder_get_display_name(type)), QVariant(QString::fromUtf8(type))); } connect(streamingEncoder, &QComboBox::currentIndexChanged, [streamingAdvancedLayout, this] { int row = 0; streamingAdvancedLayout->getWidgetPosition(streamingEncoder, &row, nullptr); for (int i = streamingAdvancedLayout->rowCount() - 1; i > row; i--) { streamingAdvancedLayout->removeRow(i); } if (streamingEncoder->currentIndex() < 0) return; auto encoder_string = streamingEncoder->currentData().toString().toUtf8(); auto encoder = encoder_string.constData(); obs_data_release(stream_encoder_settings); stream_encoder_settings = obs_encoder_defaults(encoder); obs_data_apply(stream_encoder_settings, canvasDock->stream_encoder_settings); if (stream_encoder_properties) obs_properties_destroy(stream_encoder_properties); stream_encoder_property_widgets.clear(); stream_encoder_properties = obs_get_encoder_properties(encoder); obs_property_t *property = obs_properties_first(stream_encoder_properties); while (property) { AddProperty(property, stream_encoder_settings, streamingAdvancedLayout, &stream_encoder_property_widgets); obs_property_next(&property); } QMetaObject::invokeMethod(streamingUseMain, "stateChanged", Q_ARG(int, streamingUseMain->checkState())); }); streamingAdvancedLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Encoder.Video")), streamingEncoder); QMetaObject::invokeMethod(streamingEncoder, "currentIndexChanged", Q_ARG(int, streamingEncoder->currentIndex())); streamingAdvancedGroup->setLayout(streamingAdvancedLayout); vb = new QVBoxLayout; vb->setContentsMargins(0, 0, 0, 0); // multistream is active, show warning if (canvasDock->disable_stream_settings) { auto multistream_warning_widget = new QGroupBox(QString::fromUtf8(obs_frontend_get_locale_string("Warning"))); auto multistream_warning_layout = new QVBoxLayout; multistream_warning_widget->setStyleSheet( "QGroupBox { background: #332701; border: 1px solid #997404; color: #ffda6a; padding-top: 16px; }"); auto multistream_warning_label = new QLabel(QString::fromUtf8(obs_module_text("OutputsMultistream"))); multistream_warning_label->setStyleSheet("color: #ffda6a; font-weight: bold;"); multistream_warning_layout->addWidget(multistream_warning_label); multistream_warning_widget->setLayout(multistream_warning_layout); vb->addWidget(multistream_warning_widget); } auto multitrackGroup = new QGroupBox(QString::fromUtf8(obs_frontend_get_locale_string("Multitrack"))); auto multitrackLayout = new QVBoxLayout; multitrackGroup->setLayout(multitrackLayout); multitrackLabel = new QLabel(QString::fromUtf8(obs_module_text("MultitrackDisabled"))); multitrackLayout->addWidget(multitrackLabel); vb->addWidget(multitrackGroup); vb->addWidget(streamingGroup); vb->addWidget(streamingAdvancedGroup); vb->addWidget(streamingDelayGroup); vb->addStretch(); streamingPage->setLayout(vb); auto recordGroup = new QGroupBox; recordGroup->setStyleSheet(QString("QGroupBox{ padding-top: 4px;}")); auto recordLayout = new QFormLayout; recordLayout->setContentsMargins(9, 2, 9, 9); recordLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); recordLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter); auto record_title_layout = new QHBoxLayout; auto record_title = new QLabel(QString::fromUtf8(obs_module_text("Recording"))); record_title->setStyleSheet(QString::fromUtf8("font-weight: bold;")); record_title_layout->addWidget(record_title, 0, Qt::AlignLeft); guide_link = new QLabel(QString::fromUtf8("") + QString::fromUtf8(obs_module_text("ViewGuide")) + QString::fromUtf8("")); guide_link->setOpenExternalLinks(true); record_title_layout->addWidget(guide_link, 0, Qt::AlignRight); recordLayout->addRow(record_title_layout); QLayout *recordPathLayout = new QHBoxLayout(); recordPath = new QLineEdit(); recordPath->setReadOnly(true); button = new QPushButton(QString::fromUtf8(obs_frontend_get_locale_string("Browse"))); button->setProperty("themeID", "settingsButtons"); connect(button, &QPushButton::clicked, [this] { const QString dir = QFileDialog::getExistingDirectory( this, QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Simple.SavePath")), recordPath->text(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); if (dir.isEmpty()) return; recordPath->setText(dir); }); recordPathLayout->addWidget(recordPath); recordPathLayout->addWidget(button); recordLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Simple.SavePath")), recordPathLayout); recordVideoBitrate = new QSpinBox; recordVideoBitrate->setSuffix(" Kbps"); recordVideoBitrate->setMinimum(2000); recordVideoBitrate->setMaximum(1000000); recordLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.VideoBitrate")), recordVideoBitrate); recordingMatchMain = new QCheckBox(QString::fromUtf8(obs_module_text("RecordingMatchMain"))); recordLayout->addWidget(recordingMatchMain); auto max_time_layout = new QHBoxLayout(); maxTimeEnable = new QCheckBox(); max_time_layout->addWidget(maxTimeEnable); maxTime = new QSpinBox(); maxTime->setMinimum(0); maxTime->setMaximum(31536000); maxTime->setSuffix(" s"); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(maxTimeEnable, &QCheckBox::checkStateChanged, [this] { maxTime->setEnabled(maxTimeEnable->isChecked()); }); #else connect(maxTimeEnable, &QCheckBox::stateChanged, [this] { maxTime->setEnabled(maxTimeEnable->isChecked()); }); #endif max_time_layout->addWidget(maxTime, 1); recordLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.SplitFile.Time")), max_time_layout); auto max_size_layout = new QHBoxLayout(); maxSizeEnable = new QCheckBox(); max_size_layout->addWidget(maxSizeEnable); maxSize = new QSpinBox(); maxSize->setMinimum(0); maxSize->setMaximum(1073741824); maxSize->setSuffix(" MB"); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(maxSizeEnable, &QCheckBox::checkStateChanged, [this] { maxSize->setEnabled(maxSizeEnable->isChecked()); }); #else connect(maxSizeEnable, &QCheckBox::stateChanged, [this] { maxSize->setEnabled(maxSizeEnable->isChecked()); }); #endif max_size_layout->addWidget(maxSize, 1); recordLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.SplitFile.Size")), max_size_layout); otherHotkey = nullptr; hotkey = GetHotkeyByName("VerticalCanvasDockStartRecording"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); otherHotkey = hw; auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_module_text("StartRecordingHotkey"))); hw->label = label; recordLayout->addRow(label, hw); hotkeys.push_back(hw); } hotkey = GetHotkeyByName("VerticalCanvasDockStopRecording"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_module_text("StopRecordingHotkey"))); hw->label = label; recordLayout->addRow(label, hw); hotkeys.push_back(hw); if (otherHotkey) { hw->label->pairPartner = otherHotkey->label; otherHotkey->label->pairPartner = hw->label; } } otherHotkey = nullptr; hotkey = GetHotkeyByName("VerticalCanvasDockPause"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); otherHotkey = hw; auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Main.PauseRecording"))); hw->label = label; recordLayout->addRow(label, hw); hotkeys.push_back(hw); } hotkey = GetHotkeyByName("VerticalCanvasDockUnpause"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Main.UnpauseRecording"))); hw->label = label; recordLayout->addRow(label, hw); hotkeys.push_back(hw); if (otherHotkey) { hw->label->pairPartner = otherHotkey->label; otherHotkey->label->pairPartner = hw->label; } } hotkey = GetHotkeyByName("VerticalCanvasDockChapter"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Main.AddChapterMarker"))); hw->label = label; recordLayout->addRow(label, hw); hotkeys.push_back(hw); } hotkey = GetHotkeyByName("VerticalCanvasDockSplit"); if (hotkey) { auto id = obs_hotkey_get_id(hotkey); std::vector combos = GetCombosForHotkey(id); auto hn = obs_hotkey_get_name(hotkey); auto hw = new OBSHotkeyWidget(this, id, hn, combos); auto label = new OBSHotkeyLabel; label->setText(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Main.SplitFile"))); hw->label = label; recordLayout->addRow(label, hw); hotkeys.push_back(hw); } recordGroup->setLayout(recordLayout); auto recordingAdvancedGroup = new QGroupBox(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Advanced"))); auto recordingAdvancedLayout = new QFormLayout; recordingAdvancedLayout->setContentsMargins(9, 2, 9, 9); recordingAdvancedLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); recordingAdvancedLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); recordingUseMain = new QCheckBox(QString::fromUtf8(obs_module_text("UseMain"))); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(recordingUseMain, &QCheckBox::checkStateChanged, [this, recordingAdvancedLayout] { #else connect(recordingUseMain, &QCheckBox::stateChanged, [this, recordingAdvancedLayout] { #endif bool checked = recordingUseMain->isChecked(); recordVideoBitrate->setEnabled(checked); for (int i = 1; i < recordingAdvancedLayout->rowCount(); i++) { auto field = recordingAdvancedLayout->itemAt(i, QFormLayout::FieldRole); if (field) { auto w = field->widget(); if (w) w->setEnabled(!checked); } auto label = recordingAdvancedLayout->itemAt(i, QFormLayout::LabelRole); if (label) { auto w = label->widget(); if (w) w->setEnabled(!checked); } } }); recordingAdvancedLayout->addWidget(recordingUseMain); filenameFormat = new QLineEdit; QStringList specList = QString::fromUtf8(obs_frontend_get_locale_string("FilenameFormatting.completer")).split(QRegularExpression("\n")); filenameFormat->setText(specList.first()); QCompleter *specCompleter = new QCompleter(specList); specCompleter->setCaseSensitivity(Qt::CaseSensitive); specCompleter->setFilterMode(Qt::MatchContains); filenameFormat->setCompleter(specCompleter); filenameFormat->setToolTip(QString::fromUtf8(obs_frontend_get_locale_string("FilenameFormatting.TT"))); connect(filenameFormat, &QLineEdit::textEdited, [this](const QString &text) { QString safeStr = text; #ifdef __APPLE__ safeStr.replace(QRegularExpression("[:]"), ""); #elif defined(_WIN32) safeStr.replace(QRegularExpression("[<>:\"\\|\\?\\*]"), ""); #else // TODO: Add filtering for other platforms #endif if (text != safeStr) filenameFormat->setText(safeStr); }); recordingAdvancedLayout->addRow( QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Adv.Recording.Filename")), filenameFormat); fileFormat = new QComboBox; fileFormat->addItem(QString::fromUtf8("mp4")); fileFormat->addItem(QString::fromUtf8("hybrid_mp4")); fileFormat->addItem(QString::fromUtf8("fragmented_mp4")); fileFormat->addItem(QString::fromUtf8("mov")); fileFormat->addItem(QString::fromUtf8("fragmented_mov")); fileFormat->addItem(QString::fromUtf8("mkv")); fileFormat->addItem(QString::fromUtf8("ts")); fileFormat->addItem(QString::fromUtf8("mp3u8")); recordingAdvancedLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Format")), fileFormat); audioTracks = new QFrame; audioTracks->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); audioTracks->setContentsMargins(0, 0, 0, 0); al = new QHBoxLayout; al->setContentsMargins(0, 0, 0, 0); audioTracks->setLayout(al); for (int i = 1; i <= MAX_AUDIO_MIXES; i++) { auto check = new QCheckBox(QString::fromUtf8("%1").arg(i)); recordingAudioTracks.push_back(check); al->addWidget(check); } recordingAdvancedLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Adv.AudioTrack")), audioTracks); recordingEncoder = new QComboBox; recordingEncoder->addItem( QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Adv.Recording.UseStreamEncoder")), QVariant(QString::fromUtf8(""))); idx = 0; while (obs_enum_encoder_types(idx++, &type)) { if (obs_get_encoder_type(type) != OBS_ENCODER_VIDEO) continue; uint32_t caps = obs_get_encoder_caps(type); if ((caps & (OBS_ENCODER_CAP_DEPRECATED | OBS_ENCODER_CAP_INTERNAL)) != 0) continue; recordingEncoder->addItem(QString::fromUtf8(obs_encoder_get_display_name(type)), QVariant(QString::fromUtf8(type))); } connect(recordingEncoder, &QComboBox::currentIndexChanged, [recordingAdvancedLayout, this] { int row = 0; recordingAdvancedLayout->getWidgetPosition(recordingEncoder, &row, nullptr); for (int i = recordingAdvancedLayout->rowCount() - 1; i > row; i--) { recordingAdvancedLayout->removeRow(i); } if (record_encoder_properties) obs_properties_destroy(record_encoder_properties); record_encoder_properties = nullptr; record_encoder_property_widgets.clear(); if (recordingEncoder->currentIndex() < 1) return; auto encoder_string = recordingEncoder->currentData().toString().toUtf8(); auto encoder = encoder_string.constData(); obs_data_release(record_encoder_settings); record_encoder_settings = obs_encoder_defaults(encoder); obs_data_apply(record_encoder_settings, canvasDock->record_encoder_settings); record_encoder_properties = obs_get_encoder_properties(encoder); obs_property_t *property = obs_properties_first(record_encoder_properties); while (property) { AddProperty(property, record_encoder_settings, recordingAdvancedLayout, &record_encoder_property_widgets); obs_property_next(&property); } QMetaObject::invokeMethod(recordingUseMain, "stateChanged", Q_ARG(int, recordingUseMain->checkState())); }); recordingAdvancedLayout->addRow(QString::fromUtf8(obs_frontend_get_locale_string("Basic.Settings.Output.Encoder.Video")), recordingEncoder); recordingAdvancedGroup->setLayout(recordingAdvancedLayout); vb = new QVBoxLayout; vb->setContentsMargins(0, 0, 0, 0); vb->addWidget(recordGroup); vb->addWidget(recordingAdvancedGroup); vb->addStretch(); recordingPage->setLayout(vb); // HELP PAGE auto helpButtonGroup = new QWidget(); auto helpButtonGroupLayout = new QVBoxLayout(); helpButtonGroupLayout->setContentsMargins(0, 0, 0, 0); helpButtonGroupLayout->setSpacing(5); helpButtonGroup->setLayout(helpButtonGroupLayout); // intro text auto helpTitle = new QLabel; helpTitle->setText(QString::fromUtf8(obs_module_text("Help"))); helpTitle->setStyleSheet(QString::fromUtf8("font-weight: bold;")); helpButtonGroupLayout->addWidget(helpTitle); // intro desc auto introDesc = new QLabel; introDesc->setText(QString::fromUtf8(obs_module_text("HelpIntro"))); helpButtonGroupLayout->addWidget(introDesc); // troubleshooter button auto tsButton = new QPushButton; tsButton->setObjectName(QStringLiteral("tsButton")); tsButton->setCheckable(false); tsButton->setText(QString::fromUtf8(obs_module_text("HelpTroubleshooterButton"))); connect(tsButton, &QPushButton::clicked, [] { QDesktopServices::openUrl(QUrl("https://vulcast.tv/docs/vertical")); }); helpButtonGroupLayout->addWidget(tsButton); // guides button auto guideButton = new QPushButton; guideButton->setObjectName(QStringLiteral("guideButton")); guideButton->setCheckable(false); guideButton->setText(QString::fromUtf8(obs_module_text("HelpGuideButton"))); connect(guideButton, &QPushButton::clicked, [] { QDesktopServices::openUrl(QUrl("https://vulcast.tv/docs/vertical")); }); helpButtonGroupLayout->addWidget(guideButton); // discord button auto discordButton = new QPushButton; discordButton->setObjectName(QStringLiteral("discordButton")); discordButton->setCheckable(false); discordButton->setText(QString::fromUtf8(obs_module_text("HelpDiscordButton"))); connect(discordButton, &QPushButton::clicked, [] { QDesktopServices::openUrl(QUrl("https://vulcast.tv/discord")); }); helpButtonGroupLayout->addWidget(discordButton); vb = new QVBoxLayout; vb->setContentsMargins(0, 0, 0, 0); vb->addWidget(helpButtonGroup); vb->addStretch(); helpPage->setLayout(vb); QPushButton *okButton = new QPushButton(QString::fromUtf8(obs_frontend_get_locale_string("OK"))); connect(okButton, &QPushButton::clicked, [this] { SaveSettings(); close(); }); QPushButton *cancelButton = new QPushButton(QString::fromUtf8(obs_frontend_get_locale_string("Cancel"))); connect(cancelButton, &QPushButton::clicked, [this] { close(); }); QHBoxLayout *contentLayout = new QHBoxLayout; contentLayout->addWidget(listWidget); contentLayout->addWidget(settingsPages, 1); QHBoxLayout *bottomLayout = new QHBoxLayout; const auto version = new QLabel(QString::fromUtf8(obs_module_text("Version")) + " " + QString::fromUtf8(PROJECT_VERSION) + " " + QString::fromUtf8("powered by") + " Vulcast"); version->setOpenExternalLinks(true); version->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); newVersion = new QLabel; newVersion->setProperty("themeID", "warning"); button->setProperty("class", "text-warning"); newVersion->setVisible(false); newVersion->setOpenExternalLinks(true); newVersion->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); bottomLayout->addWidget(version, 1, Qt::AlignLeft); bottomLayout->addWidget(newVersion, 1, Qt::AlignLeft); bottomLayout->addWidget(okButton, 0, Qt::AlignRight); bottomLayout->addWidget(cancelButton, 0, Qt::AlignRight); QVBoxLayout *vlayout = new QVBoxLayout; vlayout->setContentsMargins(11, 11, 11, 11); vlayout->addLayout(contentLayout); vlayout->addLayout(bottomLayout); setLayout(vlayout); setWindowTitle(obs_module_text("VerticalSettings")); setSizeGripEnabled(true); } OBSBasicSettings::~OBSBasicSettings() { obs_properties_destroy(stream_encoder_properties); obs_properties_destroy(record_encoder_properties); obs_data_release(stream_encoder_settings); obs_data_release(record_encoder_settings); } QIcon OBSBasicSettings::GetGeneralIcon() const { return listWidget->item(0)->icon(); } QIcon OBSBasicSettings::GetAppearanceIcon() const { return QIcon(); } QIcon OBSBasicSettings::GetStreamIcon() const { return listWidget->item(1)->icon(); } QIcon OBSBasicSettings::GetOutputIcon() const { return listWidget->item(2)->icon(); } QIcon OBSBasicSettings::GetAudioIcon() const { return QIcon(); } QIcon OBSBasicSettings::GetVideoIcon() const { return QIcon(); } QIcon OBSBasicSettings::GetHotkeysIcon() const { return QIcon(); } QIcon OBSBasicSettings::GetAccessibilityIcon() const { return QIcon(); } QIcon OBSBasicSettings::GetAdvancedIcon() const { return QIcon(); } void OBSBasicSettings::SetGeneralIcon(const QIcon &icon) { listWidget->item(0)->setIcon(icon); } void OBSBasicSettings::SetAppearanceIcon(const QIcon &icon) { UNUSED_PARAMETER(icon); } void OBSBasicSettings::SetStreamIcon(const QIcon &icon) { listWidget->item(1)->setIcon(icon); } void OBSBasicSettings::SetOutputIcon(const QIcon &icon) { listWidget->item(2)->setIcon(icon); } void OBSBasicSettings::SetAudioIcon(const QIcon &icon) { UNUSED_PARAMETER(icon); } void OBSBasicSettings::SetVideoIcon(const QIcon &icon) { UNUSED_PARAMETER(icon); } void OBSBasicSettings::SetHotkeysIcon(const QIcon &icon) { UNUSED_PARAMETER(icon); } void OBSBasicSettings::SetAccessibilityIcon(const QIcon &icon) { UNUSED_PARAMETER(icon); } void OBSBasicSettings::SetAdvancedIcon(const QIcon &icon) { UNUSED_PARAMETER(icon); } void OBSBasicSettings::AddServer() { int idx = (int)servers.size(); auto serverGroup = new QGroupBox; serverGroup->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); serverGroup->setStyleSheet(QString("QGroupBox{background-color: %1; padding-top: 4px;}") .arg(palette().color(QPalette::ColorRole::Mid).name(QColor::HexRgb))); auto serverLayout = new QFormLayout; serverLayout->setContentsMargins(9, 2, 9, 9); serverLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); serverLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); auto enabled = new QCheckBox(QString::fromUtf8(obs_module_text("Enabled"))); enabled->setChecked(true); serverLayout->addRow(enabled); servers_enabled.push_back(enabled); auto server_name = new QLineEdit; serverLayout->addRow(QString::fromUtf8(obs_module_text("Name")), server_name); server_names.push_back(server_name); auto server = new QComboBox; server->setEditable(true); server->addItem("rtmps://a.rtmps.youtube.com:443/live2"); server->addItem("rtmps://b.rtmps.youtube.com:443/live2?backup=1"); server->addItem("rtmp://a.rtmp.youtube.com/live2"); server->addItem("rtmp://b.rtmp.youtube.com/live2?backup=1"); server->setCurrentText(""); serverLayout->addRow(QString::fromUtf8(obs_module_text("Server")), server); servers.push_back(server); QLayout *subLayout = new QHBoxLayout(); auto key = new QLineEdit; key->setEchoMode(QLineEdit::Password); QPushButton *show = new QPushButton(); show->setText(QString::fromUtf8(obs_frontend_get_locale_string("Show"))); show->setCheckable(true); connect(show, &QAbstractButton::toggled, [=](bool hide) { show->setText( QString::fromUtf8(hide ? obs_frontend_get_locale_string("Hide") : obs_frontend_get_locale_string("Show"))); key->setEchoMode(hide ? QLineEdit::Normal : QLineEdit::Password); }); subLayout->addWidget(key); subLayout->addWidget(show); serverLayout->addRow(QString::fromUtf8(obs_module_text("Key")), subLayout); keys.push_back(key); serverGroup->setLayout(serverLayout); streamingLayout->insertRow(idx + 1, serverGroup); } void OBSBasicSettings::LoadSettings() { if (!canvasDock->newer_version_available.isEmpty()) { newVersion->setText(QString::fromUtf8(obs_module_text("NewVersion")).arg(canvasDock->newer_version_available)); newVersion->setVisible(true); } resolution->setCurrentText(QString::number(canvasDock->canvas_width) + "x" + QString::number(canvasDock->canvas_height)); bool enable = !obs_output_active(canvasDock->recordOutput) && !obs_output_active(canvasDock->virtualCamOutput); for (auto it = canvasDock->streamOutputs.begin(); it != canvasDock->streamOutputs.end(); ++it) { if (obs_output_active(it->output)) enable = false; } resolution->setEnabled(enable); virtualCameraMode->setCurrentIndex(canvasDock->virtual_cam_mode); recordVideoBitrate->setValue(canvasDock->recordVideoBitrate ? canvasDock->recordVideoBitrate : 6000); maxTimeEnable->setChecked(canvasDock->max_time_sec > 0); maxTime->setValue(canvasDock->max_time_sec); maxSizeEnable->setChecked(canvasDock->max_size_mb > 0); maxSize->setValue(canvasDock->max_size_mb); recordingMatchMain->setChecked(canvasDock->recordingMatchMain); streamingVideoBitrate->setValue(canvasDock->streamingVideoBitrate ? canvasDock->streamingVideoBitrate : 6000); streamingMatchMain->setChecked(canvasDock->streamingMatchMain); for (int i = 0; i < audioBitrate->count(); i++) { if (audioBitrate->itemData(i).toUInt() == (canvasDock->audioBitrate ? canvasDock->audioBitrate : 160)) { audioBitrate->setCurrentIndex(i); } } backtrackClip->setChecked(canvasDock->startReplay); backtrackDuration->setValue(canvasDock->replayDuration); backtrackPath->setText(QString::fromUtf8(canvasDock->replayPath)); auto profile_config = obs_frontend_get_profile_config(); if (config_get_bool(profile_config, "Stream1", "EnableMultitrackVideo")) { auto canvas_id = config_get_string(profile_config, "Stream1", "MultitrackExtraCanvas"); if (canvas_id && strcmp(canvas_id, obs_canvas_get_uuid(canvasDock->canvas)) == 0) { multitrackLabel->setText(QString::fromUtf8(obs_module_text("MultitrackVerticalSelected"))); } else { multitrackLabel->setText(QString::fromUtf8(obs_module_text("MultitrackVerticalNotSelected"))); } } else { multitrackLabel->setText(QString::fromUtf8(obs_module_text("MultitrackDisabled"))); } if (!canvasDock->disable_stream_settings) { for (size_t idx = 0; idx < canvasDock->streamOutputs.size(); idx++) { if (idx >= servers.size()) { AddServer(); } auto name = canvasDock->streamOutputs[idx].name; server_names[idx]->setText(name.empty() ? QString::fromUtf8(obs_module_text("Output")) + " " + QString::number(idx + 1) : QString::fromUtf8(name)); auto key = keys[idx]; key->setEchoMode(QLineEdit::Password); key->setText(QString::fromUtf8(canvasDock->streamOutputs[idx].stream_key)); servers[idx]->setCurrentText(QString::fromUtf8(canvasDock->streamOutputs[idx].stream_server)); servers_enabled[idx]->setChecked(canvasDock->streamOutputs[idx].enabled); } if (servers.empty()) { AddServer(); } } streamingUseMain->setChecked(!canvasDock->stream_advanced_settings); if (canvasDock->stream_audio_track > 0) streamingAudioTracks[canvasDock->stream_audio_track - 1]->setChecked(true); streamDelayEnable->setChecked(canvasDock->stream_delay_enabled); streamDelayDuration->setValue(canvasDock->stream_delay_duration); streamDelayPreserve->setChecked(canvasDock->stream_delay_preserve); auto idx = streamingEncoder->findData(QVariant(QString::fromUtf8(canvasDock->stream_encoder.c_str()))); if (idx != -1) streamingEncoder->setCurrentIndex(idx); for (const auto &kv : stream_encoder_property_widgets) { LoadProperty(kv.first, canvasDock->stream_encoder_settings, kv.second); } recordPath->setText(QString::fromUtf8(canvasDock->recordPath)); recordingUseMain->setChecked(!canvasDock->record_advanced_settings); if (!canvasDock->filename_formatting.empty()) filenameFormat->setText(canvasDock->filename_formatting.c_str()); idx = fileFormat->findData(QVariant(QString::fromUtf8(canvasDock->file_format.c_str()))); if (idx == -1) { idx = fileFormat->findText(QString::fromUtf8(canvasDock->file_format.c_str())); } if (idx != -1 && idx != fileFormat->currentIndex()) fileFormat->setCurrentIndex(idx); for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { recordingAudioTracks[i]->setChecked((canvasDock->record_audio_tracks & (1ll << i)) != 0); } idx = recordingEncoder->findData(QVariant(QString::fromUtf8(canvasDock->record_encoder.c_str()))); if (idx != -1) recordingEncoder->setCurrentIndex(idx); for (const auto &kv : record_encoder_property_widgets) { LoadProperty(kv.first, canvasDock->record_encoder_settings, kv.second); } } void OBSBasicSettings::SaveSettings() { for (auto &hw : hotkeys) { hw->Save(); } const auto res = resolution->currentText(); uint32_t width, height; if (sscanf(res.toUtf8().constData(), "%dx%d", &width, &height) == 2 && width > 0 && height > 0 && (width != canvasDock->canvas_width || height != canvasDock->canvas_height)) { if (obs_output_active(canvasDock->replayOutput)) obs_output_stop(canvasDock->replayOutput); blog(LOG_INFO, "[Vertical Canvas] resolution changed from %dx%d to %dx%d", canvasDock->canvas_width, canvasDock->canvas_height, width, height); canvasDock->canvas_width = width; canvasDock->canvas_height = height; obs_video_info ovi; obs_get_video_info(&ovi); ovi.base_width = width; ovi.base_height = height; ovi.output_width = width; ovi.output_height = height; obs_canvas_reset_video(canvasDock->canvas, &ovi); auto t = obs_weak_source_get_source(canvasDock->source); if (obs_source_get_type(t) == OBS_SOURCE_TYPE_TRANSITION) { obs_transition_set_size(t, width, height); } obs_source_release(t); canvasDock->LoadScenes(); canvasDock->ProfileChanged(); } if (virtualCameraMode->currentIndex() >= 0) canvasDock->virtual_cam_mode = virtualCameraMode->currentIndex(); uint32_t bitrate = (uint32_t)recordVideoBitrate->value(); if (bitrate != canvasDock->recordVideoBitrate) { canvasDock->recordVideoBitrate = bitrate; SetEncoderBitrate(obs_output_get_video_encoder(canvasDock->replayOutput), true); SetEncoderBitrate(obs_output_get_video_encoder(canvasDock->recordOutput), true); } canvasDock->max_size_mb = maxSizeEnable->isChecked() ? maxSize->value() : 0; canvasDock->max_time_sec = maxTimeEnable->isChecked() ? maxTime->value() : 0; canvasDock->recordingMatchMain = recordingMatchMain->isChecked(); bitrate = (uint32_t)streamingVideoBitrate->value(); if (bitrate != canvasDock->streamingVideoBitrate) { canvasDock->streamingVideoBitrate = bitrate; for (auto it = canvasDock->streamOutputs.begin(); it != canvasDock->streamOutputs.end(); ++it) SetEncoderBitrate(obs_output_get_video_encoder(it->output), false); } canvasDock->streamingMatchMain = streamingMatchMain->isChecked(); bitrate = (uint32_t)audioBitrate->currentData().toUInt(); if (bitrate != canvasDock->audioBitrate) { canvasDock->audioBitrate = bitrate; for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { SetEncoderBitrate(obs_output_get_audio_encoder(canvasDock->replayOutput, i), true); SetEncoderBitrate(obs_output_get_audio_encoder(canvasDock->recordOutput, i), true); for (auto it = canvasDock->streamOutputs.begin(); it != canvasDock->streamOutputs.end(); ++it) SetEncoderBitrate(obs_output_get_audio_encoder(it->output, i), false); } } auto startReplay = backtrackClip->isChecked(); auto duration = (uint32_t)backtrackDuration->value(); std::string replayPath = backtrackPath->text().toUtf8().constData(); if (duration != canvasDock->replayDuration || replayPath != canvasDock->replayPath || canvasDock->startReplay != startReplay) { canvasDock->replayDuration = duration; canvasDock->replayPath = replayPath; canvasDock->startReplay = startReplay; if (canvasDock->replayAlwaysOn || startReplay) { if (obs_output_active(canvasDock->replayOutput)) { canvasDock->StopReplayBuffer(); QTimer::singleShot(500, this, [this] { canvasDock->CheckReplayBuffer(true); }); } else { canvasDock->CheckReplayBuffer(true); } } else { canvasDock->StopReplayBuffer(); } } if (!canvasDock->disable_stream_settings) { for (size_t idx = 0; idx < servers.size(); idx++) { std::string sk = keys[idx]->text().toUtf8().constData(); std::string ss = servers[idx]->currentText().toUtf8().constData(); if (idx >= canvasDock->streamOutputs.size()) { StreamServer so; so.stream_server = ss; so.stream_key = sk; std::string service_name = "vertical_canvas_stream_service_"; service_name += std::to_string(idx); bool whip = strstr(so.stream_server.c_str(), "whip") != nullptr; so.service = obs_service_create(whip ? "whip_custom" : "rtmp_custom", service_name.c_str(), nullptr, nullptr); canvasDock->streamOutputs.push_back(so); } canvasDock->streamOutputs[idx].name = server_names[idx]->text().toUtf8().constData(); if (sk != canvasDock->streamOutputs[idx].stream_key || ss != canvasDock->streamOutputs[idx].stream_server) { canvasDock->streamOutputs[idx].stream_key = sk; canvasDock->streamOutputs[idx].stream_server = ss; if (obs_output_active(canvasDock->streamOutputs[idx].output)) { //TODO restart //StopStream(); //StartStream(); } } canvasDock->streamOutputs[idx].enabled = servers_enabled[idx]->isChecked(); } if (canvasDock->streamOutputs.size() > servers.size()) { for (auto idx = canvasDock->streamOutputs.size() - 1; idx >= servers.size(); idx--) { if (obs_output_active(canvasDock->streamOutputs[idx].output)) obs_output_stop(canvasDock->streamOutputs[idx].output); obs_output_release(canvasDock->streamOutputs[idx].output); obs_service_release(canvasDock->streamOutputs[idx].service); canvasDock->streamOutputs.pop_back(); } } } canvasDock->UpdateMulti(); if (streamDelayEnable->isChecked() != canvasDock->stream_delay_enabled || (canvasDock->stream_delay_enabled && ((uint32_t)streamDelayDuration->value() != canvasDock->stream_delay_duration || streamDelayPreserve->isChecked() != canvasDock->stream_delay_preserve))) { canvasDock->stream_delay_enabled = streamDelayEnable->isChecked(); canvasDock->stream_delay_duration = (uint32_t)streamDelayDuration->value(); canvasDock->stream_delay_preserve = streamDelayPreserve->isChecked(); for (auto it = canvasDock->streamOutputs.begin(); it != canvasDock->streamOutputs.end(); ++it) { if (!it->output) continue; obs_output_set_delay(it->output, canvasDock->stream_delay_enabled ? canvasDock->stream_delay_duration : 0, canvasDock->stream_delay_preserve ? OBS_OUTPUT_DELAY_PRESERVE : 0); } } auto sa = !streamingUseMain->isChecked(); auto se = streamingEncoder->currentData().toString().toUtf8(); if (canvasDock->stream_advanced_settings != sa || canvasDock->stream_encoder != se.constData()) { canvasDock->stream_advanced_settings = sa; canvasDock->stream_encoder = se.constData(); obs_encoder_t *enc = nullptr; for (auto it = canvasDock->streamOutputs.begin(); it != canvasDock->streamOutputs.end(); ++it) { if (it->output && !obs_output_active(it->output)) { if (!enc) enc = obs_output_get_video_encoder(it->output); obs_output_set_video_encoder(it->output, nullptr); } } if (enc && strcmp(obs_encoder_get_name(enc), "vertical_canvas_video_encoder") == 0) { obs_encoder_release(enc); } } for (int i = 1; i <= (int)streamingAudioTracks.size(); i++) { if (streamingAudioTracks[i - 1]->isChecked()) { if (canvasDock->stream_audio_track != i) { for (auto it = canvasDock->streamOutputs.begin(); it != canvasDock->streamOutputs.end(); ++it) { if (it->output && !obs_output_active(it->output)) obs_output_set_audio_encoder(it->output, nullptr, 0); } canvasDock->stream_audio_track = i; } break; } } obs_data_apply(canvasDock->stream_encoder_settings, stream_encoder_settings); canvasDock->recordPath = recordPath->text().toUtf8().constData(); auto ra = !recordingUseMain->isChecked(); auto re = recordingEncoder->currentData().toString().toUtf8(); if (canvasDock->record_advanced_settings != ra || canvasDock->record_encoder != re.constData()) { canvasDock->record_advanced_settings = ra; canvasDock->record_encoder = re.constData(); if ((canvasDock->recordOutput && !obs_output_active(canvasDock->recordOutput)) || (canvasDock->replayOutput && !obs_output_active(canvasDock->replayOutput))) { auto enc = canvasDock->recordOutput ? obs_output_get_video_encoder(canvasDock->recordOutput) : nullptr; if (!enc && canvasDock->replayOutput) { enc = obs_output_get_video_encoder(canvasDock->replayOutput); } if (canvasDock->recordOutput) obs_output_set_video_encoder(canvasDock->recordOutput, nullptr); if (canvasDock->replayOutput) obs_output_set_video_encoder(canvasDock->replayOutput, nullptr); if (enc && strcmp(obs_encoder_get_name(enc), "vertical_canvas_record_video_encoder") == 0) { obs_encoder_release(enc); } } } canvasDock->filename_formatting = filenameFormat->text().toUtf8().constData(); canvasDock->file_format = fileFormat->currentText().toUtf8().constData(); long long tracks = 0; for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { if (recordingAudioTracks[i]->isChecked()) tracks += (1ll << i); } if (!tracks) tracks = 1; if (canvasDock->record_audio_tracks != tracks) { canvasDock->record_audio_tracks = tracks; if ((canvasDock->recordOutput && !obs_output_active(canvasDock->recordOutput)) || (canvasDock->replayOutput && !obs_output_active(canvasDock->replayOutput))) { for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { auto enc = canvasDock->recordOutput ? obs_output_get_audio_encoder(canvasDock->recordOutput, i) : nullptr; if (!enc && canvasDock->replayOutput) { enc = obs_output_get_audio_encoder(canvasDock->replayOutput, i); } if (canvasDock->recordOutput) obs_output_set_audio_encoder(canvasDock->recordOutput, nullptr, i); if (canvasDock->replayOutput) obs_output_set_audio_encoder(canvasDock->replayOutput, nullptr, i); obs_encoder_release(enc); } } } obs_data_apply(canvasDock->record_encoder_settings, record_encoder_settings); } void OBSBasicSettings::SetEncoderBitrate(obs_encoder_t *encoder, bool record) { if (!encoder) return; auto settings = obs_encoder_get_settings(encoder); if (!settings) return; auto bitrate = obs_encoder_get_type(encoder) == OBS_ENCODER_AUDIO ? canvasDock->audioBitrate : (record ? canvasDock->recordVideoBitrate : canvasDock->streamingVideoBitrate); if (obs_data_get_int(settings, "bitrate") == bitrate) { obs_data_release(settings); return; } obs_data_set_int(settings, "bitrate", bitrate); obs_encoder_update(encoder, nullptr); obs_data_release(settings); } std::vector OBSBasicSettings::GetHotKeysFromOutput(obs_output_t *output) { struct find_hotkey { std::vector hotkeys; obs_weak_output_t *output; }; find_hotkey t = {}; t.output = obs_output_get_weak_output(output); obs_enum_hotkeys( [](void *param, obs_hotkey_id, obs_hotkey_t *key) { if (obs_hotkey_get_registerer_type(key) != OBS_HOTKEY_REGISTERER_OUTPUT) return true; auto hp = (struct find_hotkey *)param; auto o = obs_hotkey_get_registerer(key); if (o == hp->output || obs_weak_output_references_output(hp->output, (obs_output_t *)o)) { hp->hotkeys.push_back(key); } return true; }, &t); obs_weak_output_release(t.output); return t.hotkeys; } std::vector OBSBasicSettings::GetCombosForHotkey(obs_hotkey_id hotkey) { struct find_combos { obs_hotkey_id hotkey; std::vector combos; }; find_combos t = {hotkey, {}}; obs_enum_hotkey_bindings( [](void *param, size_t idx, obs_hotkey_binding_t *binding) { UNUSED_PARAMETER(idx); auto fc = (struct find_combos *)param; if (fc->hotkey == obs_hotkey_binding_get_hotkey_id(binding)) { fc->combos.push_back(obs_hotkey_binding_get_key_combination(binding)); } return true; }, &t); return t.combos; } std::vector OBSBasicSettings::GetHotkeyById(obs_hotkey_id hotkey) { struct find_hotkey { std::vector hotkeys; obs_hotkey_id hotkey_id; }; find_hotkey t = {}; t.hotkey_id = hotkey; obs_enum_hotkeys( [](void *param, obs_hotkey_id id, obs_hotkey_t *key) { auto hp = (struct find_hotkey *)param; if (hp->hotkey_id == id) { hp->hotkeys.push_back(key); } return true; }, &t); return t.hotkeys; } obs_hotkey_t *OBSBasicSettings::GetHotkeyByName(QString name) { struct find_hotkey { obs_hotkey_t *hotkey; const char *name; }; find_hotkey t = {}; auto n = name.toUtf8(); t.name = n.constData(); obs_enum_hotkeys( [](void *param, obs_hotkey_id id, obs_hotkey_t *key) { UNUSED_PARAMETER(id); const auto hp = (struct find_hotkey *)param; const auto hn = obs_hotkey_get_name(key); if (strcmp(hp->name, hn) == 0) hp->hotkey = key; return true; }, &t); return t.hotkey; } void OBSBasicSettings::AddProperty(obs_property_t *property, obs_data_t *settings, QFormLayout *layout, std::map *widgets) { obs_property_type type = obs_property_get_type(property); if (type == OBS_PROPERTY_BOOL) { auto widget = new QCheckBox(QString::fromUtf8(obs_property_description(property))); widget->setChecked(obs_data_get_bool(settings, obs_property_name(property))); layout->addWidget(widget); if (!obs_property_visible(property)) { widget->setVisible(false); int row = 0; layout->getWidgetPosition(widget, &row, nullptr); auto item = layout->itemAt(row, QFormLayout::LabelRole); if (item) { auto w = item->widget(); if (w) w->setVisible(false); } } widgets->emplace(property, widget); #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) connect(widget, &QCheckBox::checkStateChanged, [this, property, settings, widget, widgets, layout] { #else connect(widget, &QCheckBox::stateChanged, [this, property, settings, widget, widgets, layout] { #endif obs_data_set_bool(settings, obs_property_name(property), widget->isChecked()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); } else if (type == OBS_PROPERTY_INT) { auto widget = new QSpinBox(); widget->setEnabled(obs_property_enabled(property)); widget->setMinimum(obs_property_int_min(property)); widget->setMaximum(obs_property_int_max(property)); widget->setSingleStep(obs_property_int_step(property)); widget->setValue((int)obs_data_get_int(settings, obs_property_name(property))); widget->setToolTip(QString::fromUtf8(obs_property_long_description(property))); widget->setSuffix(QString::fromUtf8(obs_property_int_suffix(property))); auto label = new QLabel(QString::fromUtf8(obs_property_description(property))); layout->addRow(label, widget); if (!obs_property_visible(property)) { widget->setVisible(false); label->setVisible(false); } widgets->emplace(property, widget); connect(widget, &QSpinBox::valueChanged, [this, property, settings, widget, widgets, layout] { obs_data_set_int(settings, obs_property_name(property), widget->value()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); } else if (type == OBS_PROPERTY_FLOAT) { auto widget = new QDoubleSpinBox(); widget->setEnabled(obs_property_enabled(property)); widget->setMinimum(obs_property_float_min(property)); widget->setMaximum(obs_property_float_max(property)); widget->setSingleStep(obs_property_float_step(property)); widget->setValue(obs_data_get_double(settings, obs_property_name(property))); widget->setToolTip(QString::fromUtf8(obs_property_long_description(property))); widget->setSuffix(QString::fromUtf8(obs_property_float_suffix(property))); auto label = new QLabel(QString::fromUtf8(obs_property_description(property))); layout->addRow(label, widget); if (!obs_property_visible(property)) { widget->setVisible(false); label->setVisible(false); } widgets->emplace(property, widget); connect(widget, &QDoubleSpinBox::valueChanged, [this, property, settings, widget, widgets, layout] { obs_data_set_double(settings, obs_property_name(property), widget->value()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); } else if (type == OBS_PROPERTY_TEXT) { obs_text_type text_type = obs_property_text_type(property); if (text_type == OBS_TEXT_MULTILINE) { auto widget = new QPlainTextEdit; widget->document()->setDefaultStyleSheet("font { white-space: pre; }"); widget->setTabStopDistance(40); widget->setPlainText(QString::fromUtf8(obs_data_get_string(settings, obs_property_name(property)))); auto label = new QLabel(QString::fromUtf8(obs_property_description(property))); layout->addRow(label, widget); if (!obs_property_visible(property)) { widget->setVisible(false); label->setVisible(false); } widgets->emplace(property, widget); connect(widget, &QPlainTextEdit::textChanged, [this, property, settings, widget, widgets, layout] { obs_data_set_string(settings, obs_property_name(property), widget->toPlainText().toUtf8()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); } else { auto widget = new QLineEdit(); widget->setText(QString::fromUtf8(obs_data_get_string(settings, obs_property_name(property)))); if (text_type == OBS_TEXT_PASSWORD) widget->setEchoMode(QLineEdit::Password); auto label = new QLabel(QString::fromUtf8(obs_property_description(property))); layout->addRow(label, widget); if (!obs_property_visible(property)) { widget->setVisible(false); label->setVisible(false); } widgets->emplace(property, widget); if (text_type != OBS_TEXT_INFO) { connect(widget, &QLineEdit::textChanged, [this, property, settings, widget, widgets, layout] { obs_data_set_string(settings, obs_property_name(property), widget->text().toUtf8()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); } } } else if (type == OBS_PROPERTY_LIST) { auto widget = new QComboBox(); widget->setMaxVisibleItems(40); widget->setToolTip(QString::fromUtf8(obs_property_long_description(property))); auto list_type = obs_property_list_type(property); obs_combo_format format = obs_property_list_format(property); size_t count = obs_property_list_item_count(property); for (size_t i = 0; i < count; i++) { QVariant var; if (format == OBS_COMBO_FORMAT_INT) { long long val = obs_property_list_item_int(property, i); var = QVariant::fromValue(val); } else if (format == OBS_COMBO_FORMAT_FLOAT) { double val = obs_property_list_item_float(property, i); var = QVariant::fromValue(val); } else if (format == OBS_COMBO_FORMAT_STRING) { var = QByteArray(obs_property_list_item_string(property, i)); } widget->addItem(QString::fromUtf8(obs_property_list_item_name(property, i)), var); } if (list_type == OBS_COMBO_TYPE_EDITABLE) widget->setEditable(true); auto name = obs_property_name(property); QVariant value; switch (format) { case OBS_COMBO_FORMAT_INT: value = QVariant::fromValue(obs_data_get_int(settings, name)); break; case OBS_COMBO_FORMAT_FLOAT: value = QVariant::fromValue(obs_data_get_double(settings, name)); break; case OBS_COMBO_FORMAT_STRING: value = QByteArray(obs_data_get_string(settings, name)); break; default:; } if (format == OBS_COMBO_FORMAT_STRING && list_type == OBS_COMBO_TYPE_EDITABLE) { widget->lineEdit()->setText(value.toString()); } else { auto idx = widget->findData(value); if (idx != -1) widget->setCurrentIndex(idx); } auto label = new QLabel(QString::fromUtf8(obs_property_description(property))); layout->addRow(label, widget); if (!obs_property_visible(property)) { widget->setVisible(false); label->setVisible(false); } widgets->emplace(property, widget); switch (format) { case OBS_COMBO_FORMAT_INT: connect(widget, &QComboBox::currentIndexChanged, [this, property, settings, widget, widgets, layout] { obs_data_set_int(settings, obs_property_name(property), widget->currentData().toInt()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); break; case OBS_COMBO_FORMAT_FLOAT: connect(widget, &QComboBox::currentIndexChanged, [this, property, settings, widget, widgets, layout] { obs_data_set_double(settings, obs_property_name(property), widget->currentData().toDouble()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); break; case OBS_COMBO_FORMAT_STRING: if (list_type == OBS_COMBO_TYPE_EDITABLE) { connect(widget, &QComboBox::currentTextChanged, [this, property, settings, widget, widgets, layout] { obs_data_set_string(settings, obs_property_name(property), widget->currentText().toUtf8().constData()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); } else { connect(widget, &QComboBox::currentIndexChanged, [this, property, settings, widget, widgets, layout] { obs_data_set_string(settings, obs_property_name(property), widget->currentData().toString().toUtf8().constData()); if (obs_property_modified(property, settings)) { RefreshProperties(widgets, layout); } }); } break; default:; } } else { // OBS_PROPERTY_PATH // OBS_PROPERTY_COLOR // OBS_PROPERTY_BUTTON // OBS_PROPERTY_FONT // OBS_PROPERTY_EDITABLE_LIST // OBS_PROPERTY_FRAME_RATE // OBS_PROPERTY_GROUP // OBS_PROPERTY_COLOR_ALPHA } obs_property_modified(property, settings); } void OBSBasicSettings::LoadProperty(obs_property_t *prop, obs_data_t *settings, QWidget *widget) { auto type = obs_property_get_type(prop); if (type == OBS_PROPERTY_BOOL) { ((QCheckBox *)widget)->setChecked(obs_data_get_bool(settings, obs_property_name(prop))); } else if (type == OBS_PROPERTY_INT) { ((QSpinBox *)widget)->setValue((int)obs_data_get_int(settings, obs_property_name(prop))); } else if (type == OBS_PROPERTY_FLOAT) { ((QDoubleSpinBox *)widget)->setValue(obs_data_get_double(settings, obs_property_name(prop))); } else if (type == OBS_PROPERTY_TEXT) { obs_text_type text_type = obs_property_text_type(prop); if (text_type == OBS_TEXT_MULTILINE) { ((QPlainTextEdit *)widget) ->setPlainText(QString::fromUtf8(obs_data_get_string(settings, obs_property_name(prop)))); } else if (text_type != OBS_TEXT_INFO) { ((QLineEdit *)widget)->setText(QString::fromUtf8(obs_data_get_string(settings, obs_property_name(prop)))); } } else if (type == OBS_PROPERTY_LIST) { obs_combo_format format = obs_property_list_format(prop); auto name = obs_property_name(prop); QVariant value; switch (format) { case OBS_COMBO_FORMAT_INT: value = QVariant::fromValue(obs_data_get_int(settings, name)); break; case OBS_COMBO_FORMAT_FLOAT: value = QVariant::fromValue(obs_data_get_double(settings, name)); break; case OBS_COMBO_FORMAT_STRING: value = QByteArray(obs_data_get_string(settings, name)); break; default:; } if (format == OBS_COMBO_FORMAT_STRING && obs_property_list_type(prop) == OBS_COMBO_TYPE_EDITABLE) { ((QComboBox *)widget)->lineEdit()->setText(value.toString()); } else { auto idx = ((QComboBox *)widget)->findData(value); if (idx != -1) ((QComboBox *)widget)->setCurrentIndex(idx); } } else { // OBS_PROPERTY_PATH // OBS_PROPERTY_COLOR // OBS_PROPERTY_BUTTON // OBS_PROPERTY_FONT // OBS_PROPERTY_EDITABLE_LIST // OBS_PROPERTY_FRAME_RATE // OBS_PROPERTY_GROUP // OBS_PROPERTY_COLOR_ALPHA } } void OBSBasicSettings::RefreshProperties(std::map *widgets, QFormLayout *layout) { if (widgets == &stream_encoder_property_widgets) { obs_property_t *property = obs_properties_first(stream_encoder_properties); while (property) { auto widget = widgets->at(property); auto visible = obs_property_visible(property); if (widget->isVisible() != visible) { widget->setVisible(visible); int row = 0; layout->getWidgetPosition(widget, &row, nullptr); auto item = layout->itemAt(row, QFormLayout::LabelRole); if (item) { widget = item->widget(); if (widget) widget->setVisible(visible); } } obs_property_next(&property); } } else if (widgets == &record_encoder_property_widgets) { obs_property_t *property = obs_properties_first(record_encoder_properties); while (property) { auto widget = widgets->at(property); auto visible = obs_property_visible(property); if (widget->isVisible() != visible) { widget->setVisible(visible); int row = 0; layout->getWidgetPosition(widget, &row, nullptr); auto item = layout->itemAt(row, QFormLayout::LabelRole); if (item) { widget = item->widget(); if (widget) widget->setVisible(visible); } } obs_property_next(&property); } } }