我正在尝试为 OBS Studio 制作一个插件,它将添加自定义源类型对象。
OBS 提供了一种简单的方法让用户使用函数 get_properties() 显示和更改源属性,但遗憾的是这对于我的插件来说还不够。相反,我创建一个全新的 QWidget 作为我的源属性主窗口,并关闭 OBS studio 打开的默认窗口。
更准确地说,我正在做以下事情:
get_properties()
时,它将创建一个新的 GUIHandler
(如果尚不存在)并调用 GUIHandler::show()
。GUIHandler::show()
将关闭OBS Studio创建的QWidget(我使用QApplication::topLevelWindows()
来找到它),然后将创建并显示我的自定义OBSGraphicsMainWindow(继承自QWidget)。当然,我的 OBSGraphicsMainWindow 可以访问源对象中存储的一些变量,以便读取和写入一些值。
到目前为止,一切工作正常,但当我需要删除窗口时,问题就开始了。基本上,删除窗口有以下三种情况:
第一个选项不是问题,但我找不到在
GUIHandler
析构函数中删除窗口的好方法,每当源被销毁时都会调用该析构函数。
这就是我到目前为止所做的:
GUIHandler::~GUIHandler()
{
if (window != nullptr) {
window->onDeletion = nullptr;
window->deleteLater();
}
}
这是有点工作,但问题是
deleteLater()
会在非线程安全实现中删除OBSGraphicsMainWindow,有时它会尝试访问已删除的源对象变量,这可能让 OBS 崩溃。
我更愿意使用
delete window;
来代替,但据我所知,我无法这样做,因为我的 OBSGraphicsMainWindow QWidget 不是在与 OBS Studio QWidget 相同的线程上创建的。因此,destroyed()
信号将由OBS Studio线程发出,但接收器是我的源线程,这在Qt中是不允许的:
QCoreApplication::sendEvent 中的 ASSERT 失败:“无法将事件发送到不同线程拥有的对象。当前线程 0x0x2807a1a5060。接收器“”(类型为“QWidget”)是在线程 0x0x2807457c590 中创建的”如果我尝试使用
window->close()
,程序将永远挂起并冻结。
当需要从 OBS Studio 主线程关闭时,您认为删除在另一个线程中创建的自定义 OBSGraphicsMainWindow QWidget 对象的最佳方法是什么?我是否应该将 deleteLater()
与两个线程共享的
std::mutex
一起使用,并让线程在删除源对象之前等待删除所有 Qt 对象? 我不得更改 OBS Studio 代码本身。提前感谢您的回答! 编辑:
我更改了
GUIHandler
析构函数。这是课程:
#pragma once
#include <functional>
#include <mutex>
class OBSGraphicsMainWindow;
class OBSBlueprintGraph;
class GUIHandler {
public:
GUIHandler(OBSBlueprintGraph* g);
~GUIHandler();
void show() const;
private:
std::condition_variable cond{};
OBSGraphicsMainWindow* window = nullptr;
OBSBlueprintGraph* graph;
#include "gui-graph.h"
#include <iostream>
#include <QApplication>
#include <QScreen>
#include <QWindow>
#include "obs-frontend-api.h"
#include "obs-graphics-main-window.h"
#include "Core/obs-blueprint-graph.h"
#include "Helpers/global-logger.h"
GUIHandler::GUIHandler(OBSBlueprintGraph *g) : graph(g)
{
QWidget *parent = static_cast<QWidget *>(obs_frontend_get_main_window());
window = new OBSGraphicsMainWindow(graph, parent, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);
window->deleteComplete = [this] {
GDebug("[GUI] >> Main Window DESTROYED! <<");
window = nullptr;
cond.notify_all();
};
}
GUIHandler::~GUIHandler()
{
if (window != nullptr) {
std::mutex mtx{};
std::unique_lock lock{mtx};
window->deleteLater();
cond.wait_for(lock, std::chrono::milliseconds(100)); // TODO improve this. For now GUI is still deleted AFTER graph if closed
if (window != nullptr) {
window->context().onDeletion.execute();
}
}
}
void GUIHandler::show() const
{
// Hide default OBS properties window, TODO how to do it better? maybe even prevent properties window creation?
const QWindowList list = QApplication::topLevelWindows();
// Reverse list loop for better performance
QList<QWindow*>::const_iterator it = list.constEnd();
while(it != list.constBegin()) {
--it;
if((*it)->objectName() == "OBSBasicPropertiesWindow") {
(*it)->close();
break;
}
}
window->adjustSize(); // Size to content
// Center window on screen
QRect screenRect = QApplication::primaryScreen()->geometry();
QRect windowRect = window->geometry();
window->move((screenRect.width() - windowRect.width())/2, (screenRect.height() - windowRect.height())/2);
window->show();
}
当
OBSGraphicsMainWindow
删除其所有子 QObject 时,会调用std::function<void()> deleteComplete
。
condition_variable 结合 OBS 前端 API 回调! 来解决我的问题 如前所述,我不能简单地在
delete window;
析构函数中使用
~GUIHandler()
,因为这样当通过 obs_source_info
destroy() 函数 ptr 调用析构函数时,我会收到以下错误:
QCoreApplication::sendEvent 中的 ASSERT 失败:“无法将事件发送到不同线程拥有的对象。当前线程 0x0x2807a1a5060。接收器“”(类型为“QWidget”)是在线程 0x0x2807457c590 中创建的”为了使用
deleteLater()
并等待
OBSGraphicsMainWindow删除,我实现了一个condition_variable,它将在调用deleteLater()
后立即等待。另一方面,当OBSGraphicsMainWindow被删除时,会通知condition_variable。
从场景中删除 OBS 源时,此功能运行良好,但如果程序关闭,则会导致程序永远挂起并冻结。在深入研究 OBS Studio 后,我发现 OBS Frontent API 在源被销毁之前发送了 OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN
类型的事件(请参阅
delete window;
直接删除 OBSGraphicsMainWindow。
obs-graphics-main-window.h:
class OBSGraphicsMainWindow : public QWidget {
public:
OBSGraphicsMainWindow(OBSBlueprintGraph* g, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
~OBSGraphicsMainWindow() override;
std::function<void()> onDestructorEnd;
private:
...
};
obs-graphics-main-window.cpp:
OBSGraphicsMainWindow::OBSGraphicsMainWindow(OBSBlueprintGraph *g, QWidget *parent, Qt::WindowFlags f)
: QWidget(parent, f), graph(g)
{
...
}
OBSGraphicsMainWindow::~OBSGraphicsMainWindow()
{
delete layout;
...
if (onDestructorEnd)
onDestructorEnd();
}
gui-handler.h:
class GUIHandler {
public:
GUIHandler(OBSBlueprintGraph* g);
~GUIHandler();
static void OBSEvent(enum obs_frontend_event event, void* ptr);
void show() const;
private:
std::condition_variable cond{};
OBSGraphicsMainWindow* window = nullptr;
OBSBlueprintGraph* graph;
};
gui-handler.cpp:
GUIHandler::GUIHandler(OBSBlueprintGraph *g) : graph(g)
{
obs_frontend_add_event_callback(OBSEvent, this);
QWidget *parent = static_cast<QWidget *>(obs_frontend_get_main_window());
window = new OBSGraphicsMainWindow(graph, parent, Qt::Window | Qt::CustomizeWindowHint | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);
window->onDestructorEnd = [this] {
GDebug("[GUI] OBS Blueprint main window Destroyed!");
window = nullptr;
cond.notify_all();
};
}
GUIHandler::~GUIHandler()
{
obs_frontend_remove_event_callback(OBSEvent, this);
if (window != nullptr) {
GDebug("[GUI] Destroy OBS BP Main window using deleteLater() and lock");
std::mutex mtx{};
std::unique_lock lock{mtx};
window->deleteLater();
cond.wait_for(lock, std::chrono::seconds(1));
}
}
void GUIHandler::OBSEvent(enum obs_frontend_event event, void *ptr)
{
if (event == OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN) {
GDebug("[GUI] Destroy OBS BP Main window using OBS Frontend API Event");
GUIHandler *handler = static_cast<GUIHandler *>(ptr);
delete handler->window;
}
}
void GUIHandler::show() const
{
// Hide default OBS properties window, TODO how to do it better? maybe even prevent properties window creation?
const QWindowList list = QApplication::topLevelWindows();
// Reverse list loop for better performance
QList<QWindow*>::const_iterator it = list.constEnd();
while(it != list.constBegin()) {
--it;
if((*it)->objectName() == "OBSBasicPropertiesWindow") {
(*it)->close();
break;
}
}
window->adjustSize(); // Size to content
// Center window on screen
QRect screenRect = QApplication::primaryScreen()->geometry();
QRect windowRect = window->geometry();
window->move((screenRect.width() - windowRect.width())/2, (screenRect.height() - windowRect.height())/2);
window->show();
}