Adapt to new OBS dock API

* The plugin will no longer attempt to restore dock positions on scene
  collection change.
* When a macro is being renamed the dock widget with the current name
  will be removed.
  A new dock with the new name will have to be opened manually.
* The status dock position will be when updating to a version containing
  this changei from an older version.
This commit is contained in:
WarmUpTill 2024-08-11 16:23:04 +02:00 committed by WarmUpTill
parent cc3ea79836
commit 9522d7c0b4
9 changed files with 159 additions and 164 deletions

View File

@ -204,7 +204,6 @@ target_sources(
lib/utils/name-dialog.hpp
lib/utils/non-modal-dialog.cpp
lib/utils/non-modal-dialog.hpp
lib/utils/obs-dock.hpp
lib/utils/obs-module-helper.cpp
lib/utils/obs-module-helper.hpp
lib/utils/path-helpers.cpp

View File

@ -6,14 +6,14 @@
namespace advss {
MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
MacroDock::MacroDock(std::weak_ptr<Macro> m,
const StringVariable &runButtonText,
const StringVariable &pauseButtonText,
const StringVariable &unpauseButtonText,
const StringVariable &conditionsTrueText,
const StringVariable &conditionsFalseText,
bool enableHighlight)
: OBSDock(parent),
: QFrame(),
_runButtonText(runButtonText),
_pauseButtonText(pauseButtonText),
_unpauseButtonText(unpauseButtonText),
@ -25,19 +25,16 @@ MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
_statusText(new QLabel(conditionsFalseText.c_str())),
_macro(m)
{
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
auto macro = _macro.lock();
if (macro) {
setWindowTitle(QString::fromStdString(macro->Name()));
_run->setVisible(macro->DockHasRunButton());
_pauseToggle->setVisible(macro->DockHasPauseButton());
_statusText->setVisible(macro->DockHasStatusLabel());
} else {
setWindowTitle("<deleted macro>");
}
setFeatures(DockWidgetClosable | DockWidgetMovable |
DockWidgetFloatable);
QWidget::connect(_run, SIGNAL(clicked()), this, SLOT(RunClicked()));
QWidget::connect(_pauseToggle, SIGNAL(clicked()), this,
SLOT(PauseToggleClicked()));
@ -52,21 +49,7 @@ MacroDock::MacroDock(std::weak_ptr<Macro> m, QWidget *parent,
QWidget::connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
_timer.start(500);
// QFrame wrapper is necessary to avoid dock being partially
// transparent
QFrame *wrapper = new QFrame;
wrapper->setFrameShape(QFrame::StyledPanel);
wrapper->setFrameShadow(QFrame::Sunken);
wrapper->setLayout(layout);
setWidget(wrapper);
setFloating(true);
hide();
}
void MacroDock::SetName(const QString &name)
{
setWindowTitle(name);
setLayout(layout);
}
void MacroDock::ShowRunButton(bool value)

View File

@ -1,10 +1,9 @@
#pragma once
#include "obs-dock.hpp"
#include "variable-string.hpp"
#include <QLabel>
#include <QPushButton>
#include <QTimer>
#include <QLabel>
#include <memory>
#include <chrono>
@ -12,18 +11,16 @@ namespace advss {
class Macro;
class MacroDock : public OBSDock {
class MacroDock : public QFrame {
Q_OBJECT
public:
MacroDock(std::weak_ptr<Macro>, QWidget *parent,
const StringVariable &runButtonText,
MacroDock(std::weak_ptr<Macro>, const StringVariable &runButtonText,
const StringVariable &pauseButtonText,
const StringVariable &unpauseButtonText,
const StringVariable &conditionsTrueText,
const StringVariable &conditionsFalseText,
bool enableHighlight);
void SetName(const QString &);
void ShowRunButton(bool);
void SetRunButtonText(const StringVariable &);
void ShowPauseButton(bool);

View File

@ -1165,7 +1165,7 @@ void AdvSceneSwitcher::CopyMacro()
}
OBSDataAutoRelease data = obs_data_create();
macro->Save(data);
macro->Save(data, true);
newMacro->Load(data);
newMacro->PostLoad();
newMacro->SetName(name);

View File

@ -14,6 +14,63 @@
#include <QAction>
#include <QMainWindow>
#include <unordered_map>
#include <util/platform.h>
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(30, 0, 0)
#include <QDockWidget>
namespace {
struct DockMapEntry {
QAction *action = nullptr;
QWidget *dock = nullptr;
};
} // namespace
static std::unordered_map<const char *, DockMapEntry> dockIds;
static std::mutex dockMutex;
static bool obs_frontend_add_dock_by_id(const char *id, const char *title,
QWidget *widget)
{
std::lock_guard<std::mutex> lock(dockMutex);
if (dockIds.count(id) > 0) {
return false;
}
widget->setObjectName(id);
auto dock = new QDockWidget();
dock->setWindowTitle(title);
dock->setWidget(widget);
dock->setFloating(true);
dock->setVisible(false);
dock->setFeatures(QDockWidget::DockWidgetClosable |
QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable);
auto action = static_cast<QAction *>(obs_frontend_add_dock(dock));
if (!action) {
return false;
}
dockIds[id] = {action, dock};
return true;
}
static void obs_frontend_remove_dock(const char *id)
{
std::lock_guard<std::mutex> lock(dockMutex);
auto it = dockIds.find(id);
if (it == dockIds.end()) {
return;
}
it->second.action->deleteLater();
it->second.dock->deleteLater();
dockIds.erase(it);
}
#endif
namespace advss {
@ -258,9 +315,15 @@ int64_t Macro::MsSinceLastCheck() const
void Macro::SetName(const std::string &name)
{
const bool nameChanged = _name == name;
_name = name;
SetHotkeysDesc();
SetDockWidgetName();
if (nameChanged) {
_dockId = GenerateDockId();
}
EnableDock(_registerDock);
}
void Macro::ResetTimers()
@ -315,11 +378,6 @@ bool Macro::RunElseActions(bool ignorePause)
return RunActionsHelper(_elseActions, ignorePause);
}
bool Macro::DockIsVisible() const
{
return _dock && _dockAction && _dock->isVisible();
}
bool Macro::WasPausedSince(
const std::chrono::high_resolution_clock::time_point &time) const
{
@ -550,9 +608,11 @@ std::shared_ptr<Macro> Macro::Parent() const
return _parent.lock();
}
bool Macro::Save(obs_data_t *obj) const
bool Macro::Save(obs_data_t *obj, bool saveForCopy) const
{
obs_data_set_string(obj, "name", _name.c_str());
if (!saveForCopy) {
obs_data_set_string(obj, "name", _name.c_str());
}
obs_data_set_bool(obj, "pause", _paused);
obs_data_set_bool(obj, "parallel", _runInParallel);
obs_data_set_bool(obj, "onChange", _performActionsOnChange);
@ -568,7 +628,7 @@ bool Macro::Save(obs_data_t *obj) const
return true;
}
SaveDockSettings(obj);
SaveDockSettings(obj, saveForCopy);
SaveSplitterPos(_actionConditionSplitterPosition, obj,
"macroActionConditionSplitterPosition");
@ -813,14 +873,10 @@ bool Macro::PauseHotkeysEnabled() const
return _registerHotkeys;
}
void Macro::SaveDockSettings(obs_data_t *obj) const
void Macro::SaveDockSettings(obs_data_t *obj, bool saveForCopy) const
{
auto dockSettings = obs_data_create();
obs_data_set_bool(dockSettings, "register", _registerDock);
// The object name is used to restore the position of the dock
if (_registerDock) {
SetDockWidgetName();
}
obs_data_set_bool(dockSettings, "hasRunButton", _dockHasRunButton);
obs_data_set_bool(dockSettings, "hasPauseButton", _dockHasPauseButton);
obs_data_set_bool(dockSettings, "hasStatusLabel", _dockHasStatusLabel);
@ -833,18 +889,14 @@ void Macro::SaveDockSettings(obs_data_t *obj) const
"conditionsTrueStatusText");
_conditionsFalseStatusText.Save(dockSettings,
"conditionsFalseStatusText");
if (_dock) {
auto window = static_cast<QMainWindow *>(
obs_frontend_get_main_window());
obs_data_set_bool(dockSettings, "isFloating",
_dock->isFloating());
obs_data_set_bool(dockSettings, "isVisible", DockIsVisible());
obs_data_set_int(dockSettings, "area",
window->dockWidgetArea(_dock));
obs_data_set_string(
dockSettings, "geometry",
_dock->saveGeometry().toBase64().constData());
if (saveForCopy) {
auto uuid = GenerateDockId();
obs_data_set_string(dockSettings, "dockId", uuid.c_str());
} else {
obs_data_set_string(dockSettings, "dockId", _dockId.c_str());
}
obs_data_set_int(dockSettings, "version", 1);
obs_data_set_obj(obj, "dockSettings", dockSettings);
obs_data_release(dockSettings);
}
@ -861,8 +913,13 @@ void Macro::LoadDockSettings(obs_data_t *obj)
return;
}
if (!obs_data_has_user_value(dockSettings, "version")) {
_dockId = std::string("ADVSS-") + _name;
} else {
_dockId = obs_data_get_string(dockSettings, "dockId");
}
const bool dockEnabled = obs_data_get_bool(dockSettings, "register");
_dockIsVisible = obs_data_get_bool(dockSettings, "isVisible");
// TODO: remove these default settings in a future version
obs_data_set_default_string(
@ -890,15 +947,6 @@ void Macro::LoadDockSettings(obs_data_t *obj)
obs_data_get_bool(dockSettings, "hasStatusLabel");
_dockHighlight = obs_data_get_bool(dockSettings,
"highlightIfConditionsTrue");
_dockIsFloating = obs_data_get_bool(dockSettings, "isFloating");
_dockArea = static_cast<Qt::DockWidgetArea>(
obs_data_get_int(dockSettings, "area"));
auto geometryStr =
obs_data_get_string(dockSettings, "geometry");
if (geometryStr && strlen(geometryStr)) {
_dockGeo =
QByteArray::fromBase64(QByteArray(geometryStr));
}
}
EnableDock(dockEnabled);
obs_data_release(dockSettings);
@ -906,47 +954,29 @@ void Macro::LoadDockSettings(obs_data_t *obj)
void Macro::EnableDock(bool value)
{
if (_registerDock == value) {
return;
}
// Reset dock regardless
RemoveDock();
// Unregister dock
if (_registerDock) {
_dockIsFloating = true;
_dockGeo = QByteArray();
if (!value) {
_registerDock = value;
return;
}
// Create new dock widget
auto window =
static_cast<QMainWindow *>(obs_frontend_get_main_window());
_dock = new MacroDock(GetWeakMacroByName(_name.c_str()), window,
_runButtonText, _pauseButtonText,
_unpauseButtonText, _conditionsTrueStatusText,
_dock = new MacroDock(GetWeakMacroByName(_name.c_str()), _runButtonText,
_pauseButtonText, _unpauseButtonText,
_conditionsTrueStatusText,
_conditionsFalseStatusText, _dockHighlight);
SetDockWidgetName(); // Used by OBS to restore position
// Register new dock
_dockAction = static_cast<QAction *>(obs_frontend_add_dock(_dock));
// Note that OBSBasic::OBSInit() has precedence over the visibility and
// geometry set here.
// The function calls here are only intended to attempt to restore the
// dock status when switching scene collections.
if (InitialLoadIsComplete()) {
_dock->setVisible(_dockIsVisible);
if (window->dockWidgetArea(_dock) != _dockArea) {
window->addDockWidget(_dockArea, _dock);
}
if (_dock->isFloating() != _dockIsFloating) {
_dock->setFloating(_dockIsFloating);
}
_dock->restoreGeometry(_dockGeo);
if (!obs_frontend_add_dock_by_id(_dockId.c_str(), _name.c_str(),
_dock)) {
blog(LOG_INFO, "failed to add macro dock for macro %s",
_name.c_str());
_dock->deleteLater();
_dock = nullptr;
_registerDock = false;
return;
}
_registerDock = value;
}
@ -1043,28 +1073,22 @@ StringVariable Macro::ConditionsFalseStatusText() const
void Macro::RemoveDock()
{
if (_dock) {
_dock->close();
_dock->deleteLater();
_dockAction->deleteLater();
_dock = nullptr;
_dockAction = nullptr;
}
obs_frontend_remove_dock(_dockId.c_str());
_dock = nullptr;
}
void Macro::SetDockWidgetName() const
std::string Macro::GenerateDockId()
{
if (!_dock) {
return;
}
// Set prefix to avoid dock name conflict
_dock->setObjectName("ADVSS-" + QString::fromStdString(_name));
_dock->SetName(QString::fromStdString(_name));
#if LIBOBS_API_VER > MAKE_SEMANTIC_VERSION(30, 0, 0)
auto uuid = os_generate_uuid();
auto id = std::string("advss-macro-dock-") + std::string(uuid);
bfree(uuid);
return id;
if (!_dockAction) {
return;
}
_dockAction->setText(QString::fromStdString(_name));
#else
static std::atomic_int16_t idCounter = 0;
return std::to_string(++idCounter);
#endif
}
static void pauseCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)

View File

@ -98,7 +98,7 @@ public:
std::shared_ptr<Macro> Parent() const;
// Saving and loading
bool Save(obs_data_t *obj) const;
bool Save(obs_data_t *obj, bool saveForCopy = false) const;
bool Load(obs_data_t *obj);
// Some macros can refer to other macros, which are not yet loaded.
// Use this function to set these references after loading is complete.
@ -155,11 +155,10 @@ private:
bool RunActions(bool ignorePause);
bool RunElseActions(bool ignorePause);
bool DockIsVisible() const;
void SetDockWidgetName() const;
void SaveDockSettings(obs_data_t *obj) const;
void SaveDockSettings(obs_data_t *obj, bool saveForCopy) const;
void LoadDockSettings(obs_data_t *obj);
void RemoveDock();
static std::string GenerateDockId();
std::string _name = "";
bool _die = false;
@ -217,12 +216,8 @@ private:
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.true");
StringVariable _conditionsFalseStatusText =
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.false");
bool _dockIsFloating = true;
bool _dockIsVisible = false;
Qt::DockWidgetArea _dockArea;
QByteArray _dockGeo;
MacroDock *_dock = nullptr;
QAction *_dockAction = nullptr;
std::string _dockId = GenerateDockId();
};
void LoadMacros(obs_data_t *obj);

View File

@ -1,10 +0,0 @@
#pragma once
#include <QDockWidget>
// QDockWidget wrapper enable applying "OBSDock" stylesheet
class OBSDock : public QDockWidget {
Q_OBJECT
public:
inline OBSDock(QWidget *parent = nullptr) : QDockWidget(parent) {}
};

View File

@ -1,4 +1,5 @@
#include "status-control.hpp"
#include "log-helper.hpp"
#include "obs-module-helper.hpp"
#include "path-helpers.hpp"
#include "plugin-state-helpers.hpp"
@ -11,10 +12,30 @@
#include <QPalette>
#include <QToolBar>
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(30, 0, 0)
#include <QDockWidget>
static bool obs_frontend_add_dock_by_id(const char *id, const char *title,
QWidget *widget)
{
widget->setObjectName(id);
auto dock = new QDockWidget();
dock->setWindowTitle(title);
dock->setWidget(widget);
dock->setFloating(true);
dock->setVisible(false);
dock->setFeatures(QDockWidget::DockWidgetClosable |
QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable);
return !!obs_frontend_add_dock(dock);
}
#endif
namespace advss {
void OpenSettingsWindow();
StatusDock *dock = nullptr;
static QString colorToString(const QColor &color)
{
@ -168,16 +189,12 @@ void StatusControl::SetStatusStyleSheet(bool stopped) const
_status->setStyleSheet(style);
}
StatusDock::StatusDock(QWidget *parent) : OBSDock(parent)
StatusDockWidget::StatusDockWidget(QWidget *parent) : QFrame(parent)
{
setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
setFeatures(DockWidgetClosable | DockWidgetMovable |
DockWidgetFloatable);
// Setting a fixed object name is crucial for OBS to be able to restore
// the docks position, if the dock is not floating
setObjectName("Adv-ss-dock");
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
QAction *action = new QAction;
auto action = new QAction;
action->setProperty("themeID", QVariant(QString::fromUtf8("cogsIcon")));
action->connect(action, &QAction::triggered, OpenSettingsWindow);
const auto path = QString::fromStdString(GetDataFilePath(
@ -195,29 +212,21 @@ StatusDock::StatusDock(QWidget *parent) : OBSDock(parent)
statusControl->ButtonLayout()->setStretchFactor(statusControl->Button(),
100);
QVBoxLayout *layout = new QVBoxLayout;
auto layout = new QVBoxLayout;
layout->addWidget(statusControl);
layout->setContentsMargins(0, 0, 0, 0);
// QFrame wrapper is necessary to avoid dock being partially
// transparent
QFrame *wrapper = new QFrame;
wrapper->setFrameShape(QFrame::StyledPanel);
wrapper->setFrameShadow(QFrame::Sunken);
wrapper->setLayout(layout);
setWidget(wrapper);
setFloating(true);
hide();
setLayout(layout);
}
void SetupDock()
{
dock = new StatusDock(
static_cast<QMainWindow *>(obs_frontend_get_main_window()));
// Added for cosmetic reasons to avoid brief flash of dock window on startup
dock->setVisible(false);
obs_frontend_add_dock(dock);
auto dock = new StatusDockWidget();
if (!obs_frontend_add_dock_by_id(
"advss-status-dock",
obs_module_text("AdvSceneSwitcher.windowTitle"), dock)) {
blog(LOG_INFO, "failed to register status dock!");
dock->deleteLater();
}
}
} // namespace advss

View File

@ -1,6 +1,4 @@
#pragma once
#include "obs-dock.hpp"
#include <QPushButton>
#include <QLabel>
#include <QTimer>
@ -38,11 +36,11 @@ private:
bool _setToStopped = true;
};
class StatusDock : public OBSDock {
class StatusDockWidget : public QFrame {
Q_OBJECT
public:
StatusDock(QWidget *parent = 0);
StatusDockWidget(QWidget *parent = 0);
};
void SetupDock();