Add Vulcast Account tab - shows linked platforms from API
Some checks are pending
Build Windows DLL / Build for Windows (push) Waiting to run
Some checks are pending
Build Windows DLL / Build for Windows (push) Waiting to run
- New 'Vulcast' tab in settings dialog - API key input with show/hide toggle - Connect button fetches platform list from /api/plugin/status - Shows connected platforms with horizontal/vertical destinations - Shows stream status (live/idle) - API key saved in OBS profile config
This commit is contained in:
parent
5a4bf6649e
commit
a9d0ebe0d3
2 changed files with 200 additions and 0 deletions
|
|
@ -19,6 +19,9 @@
|
||||||
#include <QCompleter>
|
#include <QCompleter>
|
||||||
#include <QDesktopServices>
|
#include <QDesktopServices>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
|
||||||
#include "hotkey-edit.hpp"
|
#include "hotkey-edit.hpp"
|
||||||
#include "obs-module.h"
|
#include "obs-module.h"
|
||||||
|
|
@ -114,6 +117,86 @@ OBSBasicSettings::OBSBasicSettings(CanvasDock *canvas_dock, QMainWindow *parent)
|
||||||
|
|
||||||
settingsPages->addWidget(supportPage);
|
settingsPages->addWidget(supportPage);
|
||||||
|
|
||||||
|
// Vulcast Account page
|
||||||
|
listwidgetitem = new QListWidgetItem(listWidget);
|
||||||
|
listwidgetitem->setIcon(QIcon(QString::fromUtf8(":/vulcast/media/logo.png")));
|
||||||
|
listwidgetitem->setText(QString::fromUtf8("Vulcast"));
|
||||||
|
|
||||||
|
QWidget *accountPage = new QWidget;
|
||||||
|
scrollArea = new QScrollArea;
|
||||||
|
scrollArea->setWidget(accountPage);
|
||||||
|
scrollArea->setWidgetResizable(true);
|
||||||
|
scrollArea->setLineWidth(0);
|
||||||
|
scrollArea->setFrameShape(QFrame::NoFrame);
|
||||||
|
settingsPages->addWidget(scrollArea);
|
||||||
|
|
||||||
|
networkManager = new QNetworkAccessManager(this);
|
||||||
|
|
||||||
|
auto accountGroup = new QGroupBox;
|
||||||
|
accountGroup->setStyleSheet(QString("QGroupBox{ padding-top: 4px;}"));
|
||||||
|
auto accountLayout = new QFormLayout;
|
||||||
|
accountLayout->setContentsMargins(9, 2, 9, 9);
|
||||||
|
accountLayout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||||
|
accountLayout->setLabelAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||||
|
accountGroup->setLayout(accountLayout);
|
||||||
|
|
||||||
|
auto account_title_layout = new QHBoxLayout;
|
||||||
|
auto account_title = new QLabel(QString::fromUtf8("Vulcast Account"));
|
||||||
|
account_title->setStyleSheet(QString::fromUtf8("font-weight: bold;"));
|
||||||
|
account_title_layout->addWidget(account_title, 0, Qt::AlignLeft);
|
||||||
|
auto dashboard_link = new QLabel(QString::fromUtf8("<a href=\"https://vulcast.xyz/dashboard/settings\">") +
|
||||||
|
QString::fromUtf8("Get API Key") + QString::fromUtf8("</a>"));
|
||||||
|
dashboard_link->setOpenExternalLinks(true);
|
||||||
|
account_title_layout->addWidget(dashboard_link, 0, Qt::AlignRight);
|
||||||
|
accountLayout->addRow(account_title_layout);
|
||||||
|
|
||||||
|
auto apiInfoLabel = new QLabel(QString::fromUtf8("Enter your Vulcast API key to see linked platforms and stream settings. Generate one at vulcast.xyz/dashboard/settings."));
|
||||||
|
apiInfoLabel->setWordWrap(true);
|
||||||
|
apiInfoLabel->setStyleSheet("color: #999; font-size: 12px;");
|
||||||
|
accountLayout->addRow(apiInfoLabel);
|
||||||
|
|
||||||
|
apiKeyInput = new QLineEdit;
|
||||||
|
apiKeyInput->setEchoMode(QLineEdit::Password);
|
||||||
|
apiKeyInput->setPlaceholderText(QString::fromUtf8("vcast_..."));
|
||||||
|
|
||||||
|
QPushButton *showApiKey = new QPushButton(QString::fromUtf8("Show"));
|
||||||
|
showApiKey->setCheckable(true);
|
||||||
|
connect(showApiKey, &QAbstractButton::toggled, [showApiKey, this](bool checked) {
|
||||||
|
showApiKey->setText(checked ? QString::fromUtf8("Hide") : QString::fromUtf8("Show"));
|
||||||
|
apiKeyInput->setEchoMode(checked ? QLineEdit::Normal : QLineEdit::Password);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto apiKeyLayout = new QHBoxLayout;
|
||||||
|
apiKeyLayout->addWidget(apiKeyInput);
|
||||||
|
apiKeyLayout->addWidget(showApiKey);
|
||||||
|
accountLayout->addRow(QString::fromUtf8("API Key"), apiKeyLayout);
|
||||||
|
|
||||||
|
auto connectButton = new QPushButton(QString::fromUtf8("Connect"));
|
||||||
|
connect(connectButton, &QPushButton::clicked, [this] { FetchPlatformStatus(); });
|
||||||
|
accountLayout->addRow(connectButton);
|
||||||
|
|
||||||
|
apiStatus = new QLabel;
|
||||||
|
apiStatus->setStyleSheet("font-size: 12px;");
|
||||||
|
apiStatus->setVisible(false);
|
||||||
|
accountLayout->addRow(apiStatus);
|
||||||
|
|
||||||
|
auto platformGroup = new QGroupBox(QString::fromUtf8("Linked Platforms"));
|
||||||
|
platformGroup->setStyleSheet(QString("QGroupBox{ padding-top: 4px;}"));
|
||||||
|
auto platformLayout = new QVBoxLayout;
|
||||||
|
platformLayout->setContentsMargins(9, 2, 9, 9);
|
||||||
|
platformGroup->setLayout(platformLayout);
|
||||||
|
|
||||||
|
platformList = new QListWidget;
|
||||||
|
platformList->setMinimumHeight(150);
|
||||||
|
platformList->setStyleSheet("QListWidget { border: 1px solid #333; }");
|
||||||
|
platformLayout->addWidget(platformList);
|
||||||
|
|
||||||
|
auto accountPageLayout = new QVBoxLayout;
|
||||||
|
accountPageLayout->addWidget(accountGroup);
|
||||||
|
accountPageLayout->addWidget(platformGroup);
|
||||||
|
accountPageLayout->addStretch();
|
||||||
|
accountPage->setLayout(accountPageLayout);
|
||||||
|
|
||||||
connect(listWidget, &QListWidget::currentRowChanged, settingsPages, &QStackedWidget::setCurrentIndex);
|
connect(listWidget, &QListWidget::currentRowChanged, settingsPages, &QStackedWidget::setCurrentIndex);
|
||||||
|
|
||||||
auto generalGroup = new QGroupBox;
|
auto generalGroup = new QGroupBox;
|
||||||
|
|
@ -1223,6 +1306,13 @@ void OBSBasicSettings::LoadSettings()
|
||||||
for (const auto &kv : record_encoder_property_widgets) {
|
for (const auto &kv : record_encoder_property_widgets) {
|
||||||
LoadProperty(kv.first, canvasDock->record_encoder_settings, kv.second);
|
LoadProperty(kv.first, canvasDock->record_encoder_settings, kv.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load API key
|
||||||
|
auto profile_config = obs_frontend_get_profile_config();
|
||||||
|
auto saved_key = config_get_string(profile_config, "VulcastVertical", "ApiKey");
|
||||||
|
if (saved_key && strlen(saved_key) > 0) {
|
||||||
|
apiKeyInput->setText(QString::fromUtf8(saved_key));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OBSBasicSettings::SaveSettings()
|
void OBSBasicSettings::SaveSettings()
|
||||||
|
|
@ -1451,6 +1541,107 @@ void OBSBasicSettings::SaveSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_apply(canvasDock->record_encoder_settings, record_encoder_settings);
|
obs_data_apply(canvasDock->record_encoder_settings, record_encoder_settings);
|
||||||
|
|
||||||
|
// Save API key
|
||||||
|
auto profile_config = obs_frontend_get_profile_config();
|
||||||
|
auto apiKey = apiKeyInput->text().toUtf8();
|
||||||
|
config_set_string(profile_config, "VulcastVertical", "ApiKey", apiKey.constData());
|
||||||
|
config_save(profile_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OBSBasicSettings::FetchPlatformStatus()
|
||||||
|
{
|
||||||
|
auto apiKey = apiKeyInput->text().trimmed();
|
||||||
|
if (apiKey.isEmpty()) {
|
||||||
|
apiStatus->setText(QString::fromUtf8("Please enter an API key."));
|
||||||
|
apiStatus->setStyleSheet("color: #f66; font-size: 12px;");
|
||||||
|
apiStatus->setVisible(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
apiStatus->setText(QString::fromUtf8("Connecting..."));
|
||||||
|
apiStatus->setStyleSheet("color: #ff0; font-size: 12px;");
|
||||||
|
apiStatus->setVisible(true);
|
||||||
|
platformList->clear();
|
||||||
|
|
||||||
|
QNetworkRequest request(QUrl(QString::fromUtf8("https://vulcast.xyz/api/plugin/status")));
|
||||||
|
request.setRawHeader("Authorization", QString("Bearer %1").arg(apiKey).toUtf8());
|
||||||
|
request.setRawHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
QNetworkReply *reply = networkManager->get(request);
|
||||||
|
connect(reply, &QNetworkReply::finished, [this, reply] {
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
|
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
if (statusCode == 401) {
|
||||||
|
apiStatus->setText(QString::fromUtf8("Invalid API key. Generate one at vulcast.xyz/dashboard/settings"));
|
||||||
|
} else {
|
||||||
|
apiStatus->setText(QString::fromUtf8("Connection failed: %1").arg(reply->errorString()));
|
||||||
|
}
|
||||||
|
apiStatus->setStyleSheet("color: #f66; font-size: 12px;");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto doc = QJsonDocument::fromJson(reply->readAll());
|
||||||
|
auto root = doc.object();
|
||||||
|
|
||||||
|
// Show user info
|
||||||
|
auto user = root["user"].toObject();
|
||||||
|
apiStatus->setText(QString::fromUtf8("Connected as %1").arg(user["email"].toString()));
|
||||||
|
apiStatus->setStyleSheet("color: #6f6; font-size: 12px;");
|
||||||
|
|
||||||
|
// Populate platform list
|
||||||
|
auto platforms = root["platforms"].toArray();
|
||||||
|
platformList->clear();
|
||||||
|
|
||||||
|
for (auto p : platforms) {
|
||||||
|
auto platform = p.toObject();
|
||||||
|
auto name = platform["platform"].toString();
|
||||||
|
auto label = platform["label"].toString();
|
||||||
|
auto connected = platform["connected"].toBool();
|
||||||
|
auto hasKey = platform["hasStreamKey"].toBool();
|
||||||
|
auto destinations = platform["destinations"].toArray();
|
||||||
|
|
||||||
|
QString icon = connected ? (hasKey ? QString::fromUtf8("✅") : QString::fromUtf8("⚠️")) : QString::fromUtf8("🔗");
|
||||||
|
QString statusText = connected ? (hasKey ? "Ready" : "No stream key") : "Manual";
|
||||||
|
auto item = new QListWidgetItem(QString("%1 %2 — %3 (%4)").arg(icon, name, label, statusText));
|
||||||
|
platformList->addItem(item);
|
||||||
|
|
||||||
|
// Show destinations (horizontal/vertical)
|
||||||
|
for (auto d : destinations) {
|
||||||
|
auto dest = d.toObject();
|
||||||
|
auto orientation = dest["orientation"].toString();
|
||||||
|
auto destLabel = dest["label"].toString();
|
||||||
|
auto enabled = dest["enabled"].toBool();
|
||||||
|
QString orientIcon = orientation == "vertical" ? QString::fromUtf8("📱") : QString::fromUtf8("🖥️");
|
||||||
|
QString enIcon = enabled ? QString::fromUtf8(" ✓") : QString::fromUtf8(" ✗");
|
||||||
|
auto subItem = new QListWidgetItem(QString(" %1 %2 %3").arg(orientIcon, destLabel, enIcon));
|
||||||
|
subItem->setForeground(QColor("#999"));
|
||||||
|
platformList->addItem(subItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (platforms.isEmpty()) {
|
||||||
|
auto item = new QListWidgetItem(QString::fromUtf8("No platforms linked yet. Go to vulcast.xyz/dashboard/destinations"));
|
||||||
|
item->setForeground(QColor("#999"));
|
||||||
|
platformList->addItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show streams
|
||||||
|
auto streams = root["streams"].toArray();
|
||||||
|
if (!streams.isEmpty()) {
|
||||||
|
platformList->addItem(new QListWidgetItem(QString::fromUtf8("")));
|
||||||
|
platformList->addItem(new QListWidgetItem(QString::fromUtf8("📹 Streams:")));
|
||||||
|
for (auto s : streams) {
|
||||||
|
auto stream = s.toObject();
|
||||||
|
auto status = stream["status"].toString();
|
||||||
|
QString statusIcon = status == "live" ? QString::fromUtf8("🔴") : QString::fromUtf8("⚪");
|
||||||
|
auto item = new QListWidgetItem(QString(" %1 %2 (%3)").arg(statusIcon, stream["name"].toString(), status));
|
||||||
|
platformList->addItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void OBSBasicSettings::SetEncoderBitrate(obs_encoder_t *encoder, bool record)
|
void OBSBasicSettings::SetEncoderBitrate(obs_encoder_t *encoder, bool record)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
#include <qspinbox.h>
|
#include <qspinbox.h>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
#include <QRadioButton>
|
#include <QRadioButton>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
#include "hotkey-edit.hpp"
|
#include "hotkey-edit.hpp"
|
||||||
|
|
||||||
|
|
@ -78,6 +80,12 @@ private:
|
||||||
|
|
||||||
std::vector<OBSHotkeyWidget *> hotkeys;
|
std::vector<OBSHotkeyWidget *> hotkeys;
|
||||||
|
|
||||||
|
// Vulcast Account
|
||||||
|
QLineEdit *apiKeyInput;
|
||||||
|
QLabel *apiStatus;
|
||||||
|
QListWidget *platformList;
|
||||||
|
QNetworkAccessManager *networkManager;
|
||||||
|
|
||||||
QIcon GetGeneralIcon() const;
|
QIcon GetGeneralIcon() const;
|
||||||
QIcon GetAppearanceIcon() const;
|
QIcon GetAppearanceIcon() const;
|
||||||
QIcon GetStreamIcon() const;
|
QIcon GetStreamIcon() const;
|
||||||
|
|
@ -117,5 +125,6 @@ public:
|
||||||
|
|
||||||
void LoadSettings();
|
void LoadSettings();
|
||||||
void SaveSettings();
|
void SaveSettings();
|
||||||
|
void FetchPlatformStatus();
|
||||||
public slots:
|
public slots:
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue