Compare commits

..

5 commits

Author SHA1 Message Date
Vulcast
59d81726eb fix: use cmake -C cache file to avoid cmd line length limit
Some checks failed
Build Windows DLL / Build for Windows (push) Has been cancelled
2026-06-30 20:50:24 +01:00
Vulcast
cd449bb46d fix: download source zip instead of git clone (no SSH needed)
Some checks are pending
Build Windows DLL / Build for Windows (push) Waiting to run
2026-06-30 20:49:05 +01:00
Vulcast
77234228a2 fix: write CMakeUserPresets.json to avoid cmd line length limit
Some checks are pending
Build Windows DLL / Build for Windows (push) Waiting to run
2026-06-30 20:45:59 +01:00
Vulcast
d6d5bad9eb fix: use cmake presets to avoid cmd.exe line length limit
Some checks are pending
Build Windows DLL / Build for Windows (push) Waiting to run
2026-06-30 20:43:46 +01:00
Vulcast
a9d0ebe0d3 Add Vulcast Account tab - shows linked platforms from API
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
2026-06-30 20:39:11 +01:00
3 changed files with 241 additions and 19 deletions

View file

@ -1,45 +1,67 @@
#Requires -Version 5.1 #Requires -Version 5.1
<#
.SYNOPSIS
Build Vulcast Vertical OBS plugin - v8 (uses cmake cache file)
#>
param() param()
$ErrorActionPreference = 'Stop' $ErrorActionPreference = 'Stop'
$CacheDir = Join-Path $env:LOCALAPPDATA "vulcast-build" $CacheDir = Join-Path $env:LOCALAPPDATA "vulcast-build"
$PluginDir = Join-Path $CacheDir "vulcast-vertical" $PluginDir = Join-Path $CacheDir "vulcast-vertical"
$PluginBuild = Join-Path $PluginDir "build" $buildDir = Join-Path $PluginDir "build_x64"
Write-Host "=== Vulcast Vertical Build ===" -ForegroundColor Cyan Write-Host "=== Vulcast Vertical Build v8 ===" -ForegroundColor Cyan
# 1. Find VS Build Tools # 1. Find VS Build Tools
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (!(Test-Path $vswhere)) { throw "VS Build Tools not found. Install from https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2022" } if (!(Test-Path $vswhere)) { throw "VS Build Tools not found" }
$installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null $installPath = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null
if (!$installPath) { throw "VS Build Tools not found" } if (!$installPath) { throw "VS Build Tools not found" }
$vcvarsall = Join-Path $installPath "VC\Auxiliary\Build\vcvarsall.bat" $vcvarsall = Join-Path $installPath "VC\Auxiliary\Build\vcvarsall.bat"
if (!(Test-Path $vcvarsall)) { throw "vcvarsall.bat not found" }
# 2. Clone or update plugin source (deps live in .deps/ inside source - persists) # 2. Download and extract plugin source
if (!(Test-Path (Join-Path $PluginDir ".git"))) { if (!(Test-Path (Join-Path $PluginDir "CMakeLists.txt"))) {
Write-Host "[1/3] Cloning source..." -ForegroundColor Cyan Write-Host "[1/4] Downloading source..." -ForegroundColor Cyan
if (Test-Path $PluginDir) { Remove-Item $PluginDir -Recurse -Force } if (Test-Path $PluginDir) { Remove-Item $PluginDir -Recurse -Force }
git clone --depth 1 "ssh://git@git.216-75-142-55.sslip.io:2222/wawadmin/vulcast-vertical.git" $PluginDir New-Item -ItemType Directory -Path $CacheDir -Force | Out-Null
$zipPath = Join-Path $CacheDir "vulcast-vertical-latest.zip"
Invoke-WebRequest -Uri "https://vulcast.xyz/dl/vulcast-vertical-latest.zip" -OutFile $zipPath
Expand-Archive -Path $zipPath -DestinationPath $PluginDir -Force
$inner = Get-ChildItem -Path $PluginDir -Directory | Select-Object -First 1
if ($inner -and (Test-Path (Join-Path $inner.FullName "CMakeLists.txt"))) {
Get-ChildItem -Path $inner.FullName -Force | Move-Item -Destination $PluginDir -Force
Remove-Item $inner.FullName -Recurse -Force -ErrorAction SilentlyContinue
}
} else { } else {
Write-Host "[1/3] Pulling updates..." -ForegroundColor Cyan Write-Host "[1/4] Source cached." -ForegroundColor Green
git -C $PluginDir pull
} }
# 3. Build plugin (incremental - cmake caches deps in .deps/) # 3. Write cmake initial cache file (avoids long command line)
Write-Host "[2/3] Configuring cmake..." -ForegroundColor Cyan Write-Host "[2/4] Writing cmake cache..." -ForegroundColor Cyan
New-Item -ItemType Directory -Path $PluginBuild -Force | Out-Null $cacheFile = Join-Path $buildDir "vulcast-init.cmake"
New-Item -ItemType Directory -Path $buildDir -Force | Out-Null
cmd /c "call `"$vcvarsall`" x64 && cmake -S `"$PluginDir`" -B `"$PluginBuild`" -G `"Visual Studio 17 2022`" -A x64 -DCMAKE_BUILD_TYPE=Release" $cacheContent = @"
set(CMAKE_BUILD_TYPE Release CACHE STRING "")
set(CMAKE_SYSTEM_VERSION "10.0.18363.657" CACHE STRING "")
set(QT_VERSION "6" CACHE STRING "")
"@
Set-Content -Path $cacheFile -Value $cacheContent -Encoding UTF8
# 4. Configure cmake using -C flag (short command line)
Write-Host "[3/4] Configuring cmake..." -ForegroundColor Cyan
$configureCmd = "cmake -S `"$PluginDir`" -B `"$buildDir`" -G `"Visual Studio 17 2022`" -A x64 -C `"$cacheFile`""
cmd /c "call `"$vcvarsall`" x64 && $configureCmd"
if ($LASTEXITCODE -ne 0) { throw "cmake configure failed" } if ($LASTEXITCODE -ne 0) { throw "cmake configure failed" }
Write-Host "[3/3] Building DLL..." -ForegroundColor Cyan # 5. Build
cmd /c "call `"$vcvarsall`" x64 && cmake --build `"$PluginBuild`" --config Release -- /p:BuildInParallel=true" Write-Host "[4/4] Building DLL..." -ForegroundColor Cyan
cmd /c "call `"$vcvarsall`" x64 && cmake --build `"$buildDir`" --config Release"
if ($LASTEXITCODE -ne 0) { throw "Build failed" } if ($LASTEXITCODE -ne 0) { throw "Build failed" }
# Find and install DLL # Find and install DLL
$dll = Get-ChildItem -Path $PluginBuild -Recurse -Filter "vulcast-vertical.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 $dll = Get-ChildItem -Path $buildDir -Recurse -Filter "vulcast-vertical.dll" -ErrorAction SilentlyContinue | Select-Object -First 1
if (!$dll) { $dll = Get-ChildItem -Path $PluginBuild -Recurse -Filter "vertical-canvas.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 } if (!$dll) { $dll = Get-ChildItem -Path $buildDir -Recurse -Filter "vertical-canvas.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 }
if (!$dll) { $dll = Get-ChildItem -Path $PluginBuild -Recurse -Filter "*.dll" -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch "Qt6|obs" } | Select-Object -First 1 } if (!$dll) { $dll = Get-ChildItem -Path $buildDir -Recurse -Filter "*.dll" -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch "Qt6|obs" } | Select-Object -First 1 }
if ($dll) { if ($dll) {
$pluginsDir = Join-Path $env:ProgramFiles "obs-studio\obs-plugins\64bit" $pluginsDir = Join-Path $env:ProgramFiles "obs-studio\obs-plugins\64bit"

View file

@ -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)

View file

@ -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:
}; };