diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0d669744f..98de2a5f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.16...3.25)
option(ENABLE_BROWSER "Enable browser source plugin (required Chromium Embedded Framework)" OFF)
+include(cmake/obs-browser-api.cmake)
+
if(NOT ENABLE_BROWSER)
target_disable(obs-browser)
target_disable_feature(obs-browser "Browser sources are not enabled by default (set CEF_ROOT_DIR and ENABLE_BROWSER)")
@@ -36,6 +38,8 @@ target_sources(
deps/signal-restore.hpp
deps/wide-string.cpp
deps/wide-string.hpp
+ obs-browser-api-impl.cpp
+ obs-browser-api-impl.hpp
obs-browser-plugin.cpp
obs-browser-source-audio.cpp
obs-browser-source.cpp
@@ -44,7 +48,7 @@ target_sources(
target_include_directories(obs-browser PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/deps")
target_compile_features(obs-browser PRIVATE cxx_std_17)
-target_link_libraries(obs-browser PRIVATE OBS::libobs OBS::frontend-api nlohmann_json::nlohmann_json)
+target_link_libraries(obs-browser PRIVATE OBS::libobs OBS::frontend-api OBS::browser-api nlohmann_json::nlohmann_json)
if(OS_WINDOWS)
include(cmake/os-windows.cmake)
diff --git a/cmake/feature-panels.cmake b/cmake/feature-panels.cmake
index 32ad506e3..f3214e485 100644
--- a/cmake/feature-panels.cmake
+++ b/cmake/feature-panels.cmake
@@ -12,8 +12,8 @@ target_compile_definitions(browser-panels INTERFACE BROWSER_AVAILABLE)
target_sources(
obs-browser
PRIVATE # cmake-format: sortable
- panel/browser-panel-client.cpp panel/browser-panel-client.hpp panel/browser-panel-internal.hpp
- panel/browser-panel.cpp)
+ obs-browser-api-impl-panel.cpp panel/browser-panel-client.cpp panel/browser-panel-client.hpp
+ panel/browser-panel-internal.hpp panel/browser-panel.cpp)
target_link_libraries(obs-browser PRIVATE OBS::browser-panels Qt::Widgets)
diff --git a/cmake/obs-browser-api.cmake b/cmake/obs-browser-api.cmake
new file mode 100644
index 000000000..03be4f478
--- /dev/null
+++ b/cmake/obs-browser-api.cmake
@@ -0,0 +1,16 @@
+find_package(Qt6 REQUIRED Widgets)
+
+add_library(obs-browser-api INTERFACE)
+add_library(OBS::browser-api ALIAS obs-browser-api)
+
+target_sources(obs-browser-api PUBLIC $
+ $)
+
+target_link_libraries(obs-browser-api INTERFACE OBS::libobs Qt::Widgets)
+
+target_include_directories(obs-browser-api INTERFACE "$/lib"
+ "$")
+
+set_target_properties(obs-browser-api PROPERTIES PREFIX "" PUBLIC_HEADER lib/obs-browser-api.hpp)
+
+target_export(obs-browser-api)
diff --git a/cmake/obs-browser-apiConfig.cmake.in b/cmake/obs-browser-apiConfig.cmake.in
new file mode 100644
index 000000000..f26978ec7
--- /dev/null
+++ b/cmake/obs-browser-apiConfig.cmake.in
@@ -0,0 +1,9 @@
+@PACKAGE_INIT@
+
+include(CMakeFindDependencyMacro)
+
+find_dependency(libobs REQUIRED)
+find_package(Qt6 REQUIRED Widgets)
+
+include("${CMAKE_CURRENT_LIST_DIR}/@TARGETS_EXPORT_NAME@.cmake")
+check_required_components("@PROJECT_NAME@")
diff --git a/lib/obs-browser-api.hpp b/lib/obs-browser-api.hpp
new file mode 100644
index 000000000..b4f7ca2fe
--- /dev/null
+++ b/lib/obs-browser-api.hpp
@@ -0,0 +1,523 @@
+// SPDX-FileCopyrightText: 2023 tytan652
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#ifndef _OBS_BROWSER_API_H
+#define _OBS_BROWSER_API_H
+
+#include
+#include
+
+#include
+
+#define OBS_BROWSER_API_VERSION 1
+
+/* ==================== INTERNAL DEFINITIONS ==================== */
+
+inline proc_handler_t *_ph;
+
+/* ==================== INTERNAL API FUNCTIONS ==================== */
+
+static inline proc_handler_t *obs_browser_get_ph(void)
+{
+ proc_handler_t *global_ph = obs_get_proc_handler();
+ assert(global_ph != nullptr);
+
+ calldata_t cd = {0};
+ if (!proc_handler_call(global_ph, "obs_browser_api_get_ph", &cd))
+ blog(LOG_DEBUG,
+ "Unable to fetch obs-browser proc handler object. obs-browser not available?");
+ auto ph = static_cast(calldata_ptr(&cd, "ph"));
+ calldata_free(&cd);
+
+ return ph;
+}
+
+static inline bool obs_browser_ensure_ph(void)
+{
+ if (!_ph)
+ _ph = obs_browser_get_ph();
+ return _ph != nullptr;
+}
+
+/* ==================== GENERAL API FUNCTIONS ==================== */
+
+/* Return the compiled obs-browser-api version.
+ *
+ * It is highly recommended to disable the use the of API if the returned version is not supported
+ */
+static inline long long obs_browser_get_api_version()
+{
+ long long version = 0;
+
+ if (!obs_browser_ensure_ph())
+ return version;
+
+ calldata_t cd = {0};
+ proc_handler_call(_ph, "get_api_version", &cd);
+ calldata_get_int(&cd, "version", &version);
+ calldata_free(&cd);
+
+ return version;
+}
+
+/* Return the compiled QCef version.
+ *
+ * It is highly recommended to disable the use of the API if the returned version is not supported
+ */
+static inline long long obs_browser_get_gcef_version()
+{
+ long long version = 0;
+
+ if (!obs_browser_ensure_ph())
+ return version;
+
+ calldata_t cd = {0};
+ proc_handler_call(_ph, "get_qcef_version", &cd);
+ calldata_get_int(&cd, "version", &version);
+ calldata_free(&cd);
+
+ return version;
+}
+
+/* ==================== GENERAL API CLASSES ==================== */
+
+/* Wrapped QCefWidget generated through obs-browser-api procedure handler.
+ *
+ * The widget is wrapped to avoid making QCefWidget definition a public API.
+ *
+ * OBSBrowserQCefWidget are generated by OBSBrowserQCef::createWidget method
+ */
+class OBSBrowserQCefWidget : public QObject {
+ Q_OBJECT
+
+ friend class OBSBrowserQCef;
+
+ proc_handler_t *ph;
+ QWidget *qcef_widget;
+
+private Q_SLOTS:
+ void _titleChanged(const QString &title) { emit titleChanged(title); }
+
+ void _urlChanged(const QString &title) { emit urlChanged(title); }
+
+private:
+ explicit OBSBrowserQCefWidget(proc_handler_t *ph, QWidget *qcef_widget,
+ QObject *parent = nullptr)
+ : QObject(parent), ph(ph), qcef_widget(qcef_widget)
+ {
+ connect(qcef_widget, SIGNAL(titleChanged(const QString &)),
+ this, SLOT(_titleChanged(const QString &)),
+ Qt::DirectConnection);
+ connect(qcef_widget, SIGNAL(urlChanged(const QString &)), this,
+ SLOT(_urlChanged(const QString &)),
+ Qt::DirectConnection);
+
+ connect(qcef_widget, &QObject::destroyed, this, [=]() {
+ this->qcef_widget = nullptr;
+ this->deleteLater();
+ });
+ }
+
+public:
+ ~OBSBrowserQCefWidget()
+ {
+ if (qcef_widget)
+ delete qcef_widget;
+
+ proc_handler_destroy(ph);
+ }
+
+ /* Return the QCefWidget pointer as a QWidget.
+ *
+ * This is to avoid making QCefWidget definition a public API
+ */
+ QWidget *qwidget() { return qcef_widget; }
+
+ void setParent(QWidget *parent)
+ {
+ QObject::setParent(parent);
+ qcef_widget->setParent(parent);
+ }
+
+ /* Set the URL used in the widget */
+ void setUrl(const char *url)
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_widget", qcef_widget);
+ calldata_set_string(&cd, "url", url);
+ proc_handler_call(ph, "set_url", &cd);
+ calldata_free(&cd);
+ }
+
+ /* Set a javascript script to be ran at the widget's browser client startup */
+ void setStartupScript(const char *script)
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_widget", qcef_widget);
+ calldata_set_string(&cd, "script", script);
+ proc_handler_call(ph, "set_startup_script", &cd);
+ calldata_free(&cd);
+ }
+
+ /* Allow all popups in the widget */
+ void allowAllPopups(bool allow)
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_widget", qcef_widget);
+ calldata_set_bool(&cd, "allow", allow);
+ proc_handler_call(ph, "allow_all_popups", &cd);
+ calldata_free(&cd);
+ }
+
+ /* Mainly meant to be used in closeEvent (or hideEvent) function.
+ *
+ * This close the widget's browser client allowing to free resources when the widget is actually.
+ *
+ * QCefWidget::showEvent recreate the client when the widget is re-shown
+ */
+ void closeBrowser()
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_widget", qcef_widget);
+ proc_handler_call(ph, "close_browser", &cd);
+ calldata_free(&cd);
+ }
+
+ /* Reload the page */
+ void reloadPage()
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_widget", qcef_widget);
+ proc_handler_call(ph, "reload_page", &cd);
+ calldata_free(&cd);
+ }
+
+ /* Execute javascript in the widget's browser client */
+ void executeJavaScript(const char *script)
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_widget", qcef_widget);
+ calldata_set_string(&cd, "script", script);
+ proc_handler_call(ph, "execute_javascript", &cd);
+ calldata_free(&cd);
+ }
+
+Q_SIGNALS:
+ /* Wrapper of the original QCefWidget::titleChanged signal.
+ *
+ * This allow to connect this signal using its functor
+ */
+ void titleChanged(const QString &title);
+
+ /* Wrapper of the original QCefWidget::urlChanged signal.
+ *
+ * This allow to connect this signal using its functor
+ */
+ void urlChanged(const QString &url);
+};
+
+/* Wrapped QCefCookieManager generated through obs-browser-api procedure handler.
+ *
+ * The object is wrapped to avoid making QCefCookieManager a public API.
+ *
+ * OBSBrowserQCefCookieManager are generated by OBSBrowserQCef::createCookieManager method
+ */
+class OBSBrowserQCefCookieManager {
+ friend class OBSBrowserQCef;
+
+ proc_handler_t *ph;
+ void *qcef_cookie_manager;
+
+ OBSBrowserQCefCookieManager(proc_handler_t *ph,
+ void *qcef_cookie_manager)
+ : ph(ph), qcef_cookie_manager(qcef_cookie_manager)
+ {
+ }
+
+public:
+ ~OBSBrowserQCefCookieManager()
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_cookie_manager",
+ qcef_cookie_manager);
+ proc_handler_call(ph, "qcef_cookie_manager_free", &cd);
+ calldata_free(&cd);
+
+ proc_handler_destroy(ph);
+ };
+
+ /* Delete cookies matching the given url and/or the given name.
+ *
+ * Empty string (or nullptr) are wildcards for both parameters
+ */
+ bool deleteCookies(const char *url, const char *name)
+ {
+ bool success = false;
+
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_cookie_manager",
+ qcef_cookie_manager);
+ calldata_set_string(&cd, "url", url);
+ calldata_set_string(&cd, "name", name);
+ proc_handler_call(ph, "delete_cookies", &cd);
+ calldata_get_bool(&cd, "success", &success);
+ calldata_free(&cd);
+
+ return success;
+ }
+
+ /* Flush the backing store (if any) to disk */
+ bool flushStore()
+ {
+ bool success = false;
+
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_cookie_manager",
+ qcef_cookie_manager);
+ proc_handler_call(ph, "flush_store", &cd);
+ calldata_get_bool(&cd, "success", &success);
+ calldata_free(&cd);
+
+ return success;
+ }
+
+ typedef std::function cookie_exists_cb;
+
+ /* Check for a cookie with matching parameter with a callback.
+ *
+ * Note, the bool parameter of the callback is set to true if the cookie is found
+ */
+ void checkForCookie(const char *url, const char *cookie,
+ cookie_exists_cb callback)
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef_cookie_manager",
+ qcef_cookie_manager);
+ calldata_set_string(&cd, "url", url);
+ calldata_set_string(&cd, "cookie", cookie);
+ calldata_set_ptr(&cd, "callback", &callback);
+ proc_handler_call(ph, "check_for_cookie", &cd);
+ calldata_free(&cd);
+ }
+};
+
+/* Wrapped QCef generated through obs-browser-api procedure handler.
+ *
+ * The object is wrapped to avoid making QCef a public API.
+ *
+ * OBSBrowserQCef are generated by OBSBrowserQCef::createOBSBrowserQCef method
+ */
+class OBSBrowserQCef {
+ proc_handler_t *ph;
+ void *qcef;
+
+ OBSBrowserQCef(proc_handler_t *ph, void *qcef) : ph(ph), qcef(qcef){};
+
+public:
+ /* Creates and return a OBSBrowserQCef instance.
+ *
+ * Can return an empty shared pointer
+ */
+ static std::shared_ptr createOBSBrowserQCef()
+ {
+ bool ret = false;
+ proc_handler_t *ph = nullptr;
+ void *qcef = nullptr;
+
+ if (!obs_browser_ensure_ph())
+ return {};
+
+ calldata_t cd = {0};
+ ret = proc_handler_call(_ph, "create_qcef", &cd);
+ calldata_get_ptr(&cd, "ph", &ph);
+ calldata_get_ptr(&cd, "qcef", &qcef);
+ calldata_free(&cd);
+
+ if (!ret)
+ return {};
+
+ return std::shared_ptr(
+ new OBSBrowserQCef(ph, qcef));
+ }
+
+ ~OBSBrowserQCef()
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ proc_handler_call(ph, "qcef_free", &cd);
+ calldata_free(&cd);
+
+ proc_handler_destroy(ph);
+ };
+
+ /* Initialize the browser if not already initialized.
+ *
+ * Return true if already initialized
+ */
+ bool initBrowser()
+ {
+ bool already_initialized = false;
+
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ proc_handler_call(ph, "init_browser", &cd);
+ calldata_get_bool(&cd, "already_initialized",
+ &already_initialized);
+ calldata_free(&cd);
+
+ return already_initialized;
+ }
+
+ /* Return if the browser is initialized */
+ bool initialized()
+ {
+ bool initialized = false;
+
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ proc_handler_call(ph, "initialized", &cd);
+ calldata_get_bool(&cd, "initialized", &initialized);
+ calldata_free(&cd);
+
+ return initialized;
+ }
+
+ /* Return when the browser is initialized */
+ bool waitForBrowserInit()
+ {
+ bool initialized = false;
+
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ proc_handler_call(ph, "wait_for_browser_init", &cd);
+ calldata_get_bool(&cd, "initialized", &initialized);
+ calldata_free(&cd);
+
+ return initialized;
+ }
+
+ /* Creates and return a OBSBrowserQCefWidget instance.
+ *
+ * Can return nullptr
+ */
+ OBSBrowserQCefWidget *
+ createWidget(QWidget *parent, const char *url,
+ OBSBrowserQCefCookieManager *cookie_manager = nullptr)
+ {
+ bool ret = false;
+ proc_handler_t *w_ph = nullptr;
+ QWidget *qcef_widget = nullptr;
+
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ calldata_set_ptr(&cd, "qwidget_parent", parent);
+ calldata_set_string(&cd, "url", url);
+ calldata_set_ptr(&cd, "qcef_cookie_manager", cookie_manager);
+ ret = proc_handler_call(ph, "create_widget", &cd);
+ calldata_get_ptr(&cd, "ph", &w_ph);
+ qcef_widget = static_cast(
+ calldata_ptr(&cd, "qcef_widget"));
+ calldata_free(&cd);
+
+ return ret ? new OBSBrowserQCefWidget(w_ph, qcef_widget, parent)
+ : nullptr;
+ }
+
+ /* Creates and return a OBSBrowserQCefCookieManager instance.
+ *
+ * Can return an empty shared pointer
+ */
+ std::shared_ptr
+ createCookieManager(const char *storage_path,
+ bool persist_session_cookies = false)
+ {
+ bool ret = false;
+ proc_handler_t *cm_ph = nullptr;
+ void *cookie_manager = nullptr;
+
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ calldata_set_string(&cd, "storage_path", storage_path);
+ calldata_set_bool(&cd, "persist_session_cookies",
+ persist_session_cookies);
+ ret = proc_handler_call(ph, "create_cookie_manager", &cd);
+ calldata_get_ptr(&cd, "ph", &cm_ph);
+ calldata_get_ptr(&cd, "qcef_cookie_manager", &cookie_manager);
+ calldata_free(&cd);
+
+ if (!ret)
+ return {};
+
+ return std::shared_ptr(
+ new OBSBrowserQCefCookieManager(cm_ph, cookie_manager));
+ }
+
+ /* Return the cookie path */
+ BPtr getCookiePath(const char *storage_path)
+ {
+ char *cookie_path = nullptr;
+
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ calldata_set_string(&cd, "storage_path", storage_path);
+ proc_handler_call(ph, "get_cookie_path", &cd);
+ cookie_path = static_cast(calldata_ptr(&cd, "path"));
+ calldata_free(&cd);
+
+ return cookie_path;
+ }
+
+ /* Add an URL to the popup whitelist.
+ *
+ * The list is global to all QCef instances.
+ *
+ * The URL is kept until the given QObject is checked and found destroyed
+ */
+ void addPopupWhitelistUrl(const char *url, QObject *obj)
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ calldata_set_string(&cd, "url", url);
+ calldata_set_ptr(&cd, "qobject", obj);
+ proc_handler_call(ph, "add_popup_whitelist_url", &cd);
+ calldata_free(&cd);
+ }
+
+ /* Force the given URL to be a popup
+ *
+ * The list is global to all QCef instances.
+ *
+ * The URL is kept until the given QObject is checked and found destroyed
+ */
+ void addForcePopupUrl(const char *url, QObject *obj)
+ {
+ calldata_t cd = {0};
+ calldata_init(&cd);
+ calldata_set_ptr(&cd, "qcef", qcef);
+ calldata_set_string(&cd, "url", url);
+ calldata_set_ptr(&cd, "qobject", obj);
+ proc_handler_call(ph, "add_force_popup_url", &cd);
+ calldata_free(&cd);
+ }
+};
+
+#endif //_OBS_BROWSER_API_H
diff --git a/obs-browser-api-impl-panel.cpp b/obs-browser-api-impl-panel.cpp
new file mode 100644
index 000000000..e4336372e
--- /dev/null
+++ b/obs-browser-api-impl-panel.cpp
@@ -0,0 +1,337 @@
+// SPDX-FileCopyrightText: 2023 tytan652
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "obs-browser-api-impl.hpp"
+
+#include "panel/browser-panel.hpp"
+
+#include
+
+#include
+
+extern "C" int obs_browser_qcef_version_export(void);
+extern "C" QCef *obs_browser_create_qcef(void);
+
+static void qcef_free(void *, calldata_t *cd)
+{
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ if (qcef)
+ delete qcef;
+}
+
+static void qcef_init_browser(void *, calldata_t *cd)
+{
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ assert(qcef != nullptr);
+
+ calldata_set_bool(cd, "already_initialized", qcef->init_browser());
+}
+
+static void qcef_initialized(void *, calldata_t *cd)
+{
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ assert(qcef != nullptr);
+
+ calldata_set_bool(cd, "initialized", qcef->initialized());
+}
+
+static void qcef_wait_for_browser_init(void *, calldata_t *cd)
+{
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ assert(qcef != nullptr);
+
+ calldata_set_bool(cd, "initialized", qcef->wait_for_browser_init());
+}
+
+static void qcef_get_cookie_path(void *, calldata_t *cd)
+{
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ const char *c_storage_path = calldata_string(cd, "storage_path");
+ std::string storage_path;
+ BPtr path;
+ assert(qcef != nullptr);
+
+ if (c_storage_path)
+ storage_path = std::string(c_storage_path);
+
+ path = qcef->get_cookie_path(storage_path);
+ calldata_set_ptr(cd, "path", bstrdup(path));
+}
+
+static void qcef_add_popup_whitelist_url(void *, calldata_t *cd)
+{
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ const char *c_url = calldata_string(cd, "url");
+ auto obj = static_cast(calldata_ptr(cd, "qobject"));
+ std::string url;
+ assert(qcef != nullptr);
+
+ if (c_url)
+ url = std::string(c_url);
+
+ qcef->add_popup_whitelist_url(url, obj);
+}
+
+static void qcef_add_force_popup_url(void *, calldata_t *cd)
+{
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ const char *c_url = calldata_string(cd, "url");
+ auto obj = static_cast(calldata_ptr(cd, "qobject"));
+ std::string url;
+ assert(qcef != nullptr);
+
+ if (c_url)
+ url = std::string(c_url);
+
+ qcef->add_force_popup_url(url, obj);
+}
+
+static void qcef_cookie_manager_free(void *, calldata_t *cd)
+{
+ auto qcef_cookie_manager = static_cast(
+ calldata_ptr(cd, "qcef_cookie_manager"));
+ if (qcef_cookie_manager)
+ delete qcef_cookie_manager;
+}
+
+static void qcef_cookie_manager_delete_cookies(void *, calldata_t *cd)
+{
+ auto qcef_cookie_manager = static_cast(
+ calldata_ptr(cd, "qcef_cookie_manager"));
+ const char *c_url = calldata_string(cd, "url");
+ const char *c_name = calldata_string(cd, "name");
+ std::string url, name;
+ assert(qcef_cookie_manager != nullptr);
+
+ if (c_url)
+ url = std::string(c_url);
+ if (c_name)
+ name = std::string(c_name);
+
+ calldata_set_bool(cd, "success",
+ qcef_cookie_manager->DeleteCookies(url, name));
+}
+
+static void qcef_cookie_manager_flush_store(void *, calldata_t *cd)
+{
+ auto qcef_cookie_manager = static_cast(
+ calldata_ptr(cd, "qcef_cookie_manager"));
+ assert(qcef_cookie_manager != nullptr);
+
+ calldata_set_bool(cd, "success", qcef_cookie_manager->FlushStore());
+}
+
+static void qcef_cookie_manager_check_for_cookie(void *, calldata_t *cd)
+{
+ auto qcef_cookie_manager = static_cast(
+ calldata_ptr(cd, "qcef_cookie_manager"));
+ const char *c_site = calldata_string(cd, "site");
+ const char *c_cookie = calldata_string(cd, "cookie");
+ auto callback = static_cast(
+ calldata_ptr(cd, "callback"));
+ std::string site, cookie;
+ assert(qcef_cookie_manager != nullptr);
+
+ if (c_site)
+ site = std::string(c_site);
+ if (c_cookie)
+ cookie = std::string(c_cookie);
+
+ qcef_cookie_manager->CheckForCookie(site, cookie, *callback);
+}
+
+static void qcef_create_cookie_manager(void *, calldata_t *cd)
+{
+ proc_handler_t *ph = proc_handler_create();
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ const char *c_storage_path = calldata_string(cd, "storage_path");
+ bool persist_session_cookies =
+ calldata_bool(cd, "persist_session_cookies");
+ std::string storage_path;
+ assert(qcef != nullptr);
+
+ if (c_storage_path)
+ storage_path = std::string(c_storage_path);
+
+ proc_handler_add(
+ ph, "void qcef_cookie_manager_free(in ptr qcef_cookie_manager)",
+ &qcef_cookie_manager_free, nullptr);
+ proc_handler_add(
+ ph,
+ "void delete_cookies(in ptr qcef_cookie_manager, in string url, in string name, out bool success)",
+ &qcef_cookie_manager_delete_cookies, nullptr);
+ proc_handler_add(
+ ph,
+ "void flush_store(in ptr qcef_cookie_manager, out bool success)",
+ &qcef_cookie_manager_flush_store, nullptr);
+ proc_handler_add(
+ ph,
+ "void check_for_cookie(in ptr qcef_cookie_manager, in string site, in string cookie, in ptr callback)",
+ &qcef_cookie_manager_check_for_cookie, nullptr);
+
+ calldata_set_ptr(cd, "ph", ph);
+ calldata_set_ptr(cd, "cookie_manager",
+ qcef->create_cookie_manager(storage_path,
+ persist_session_cookies));
+}
+
+static void qcef_widget_set_url(void *, calldata_t *cd)
+{
+ auto qcef_widget =
+ static_cast(calldata_ptr(cd, "qcef_widget"));
+ const char *c_url = calldata_string(cd, "url");
+ std::string url;
+ assert(qcef_widget != nullptr);
+
+ if (c_url)
+ url = std::string(c_url);
+
+ qcef_widget->setURL(url);
+}
+
+static void qcef_widget_set_startup_script(void *, calldata_t *cd)
+{
+ auto qcef_widget =
+ static_cast(calldata_ptr(cd, "qcef_widget"));
+ const char *c_script = calldata_string(cd, "script");
+ std::string script;
+ assert(qcef_widget != nullptr);
+
+ if (c_script)
+ script = std::string(c_script);
+
+ qcef_widget->setStartupScript(script);
+}
+
+static void qcef_widget_allow_all_popups(void *, calldata_t *cd)
+{
+ auto qcef_widget =
+ static_cast(calldata_ptr(cd, "qcef_widget"));
+ assert(qcef_widget != nullptr);
+
+ qcef_widget->allowAllPopups(calldata_bool(cd, "allow"));
+}
+
+static void qcef_widget_close_browser(void *, calldata_t *cd)
+{
+ auto qcef_widget =
+ static_cast(calldata_ptr(cd, "qcef_widget"));
+ assert(qcef_widget != nullptr);
+
+ qcef_widget->closeBrowser();
+}
+
+static void qcef_widget_reload_page(void *, calldata_t *cd)
+{
+ auto qcef_widget =
+ static_cast(calldata_ptr(cd, "qcef_widget"));
+ assert(qcef_widget != nullptr);
+
+ qcef_widget->reloadPage();
+}
+
+static void qcef_widget_execute_javascript(void *, calldata_t *cd)
+{
+ auto qcef_widget =
+ static_cast(calldata_ptr(cd, "qcef_widget"));
+ const char *c_script = calldata_string(cd, "script");
+ std::string script;
+ assert(qcef_widget != nullptr);
+
+ if (c_script)
+ script = std::string(c_script);
+
+ qcef_widget->executeJavaScript(script);
+}
+
+static void qcef_create_widget(void *, calldata_t *cd)
+{
+ proc_handler_t *ph = proc_handler_create();
+ auto qcef = static_cast(calldata_ptr(cd, "qcef"));
+ auto parent =
+ static_cast(calldata_ptr(cd, "qwidget_parent"));
+ const char *c_url = calldata_string(cd, "url");
+ auto cookie_manager = static_cast(
+ calldata_ptr(cd, "cookie_manager"));
+ std::string url;
+ assert(qcef != nullptr);
+
+ if (c_url)
+ url = std::string(c_url);
+
+ proc_handler_add(ph, "void set_url(in ptr qcef_widget, in string url)",
+ qcef_widget_set_url, nullptr);
+ proc_handler_add(
+ ph,
+ "void set_startup_script(in ptr qcef_widget, in string script)",
+ qcef_widget_set_startup_script, nullptr);
+ proc_handler_add(
+ ph, "void allow_all_popups(in ptr qcef_widget, in bool allow)",
+ qcef_widget_allow_all_popups, nullptr);
+ proc_handler_add(ph, "void close_browser(in ptr qcef_widget)",
+ qcef_widget_close_browser, nullptr);
+ proc_handler_add(ph, "void reload_page(in ptr qcef_widget)",
+ qcef_widget_reload_page, nullptr);
+ proc_handler_add(
+ ph,
+ "void execute_javascript(in ptr qcef_widget, in string script)",
+ qcef_widget_execute_javascript, nullptr);
+
+ calldata_set_ptr(cd, "ph", ph);
+ calldata_set_ptr(cd, "qcef_widget",
+ qcef->create_widget(parent, url, cookie_manager));
+}
+
+void BrowserApi::get_qcef_version(void *, calldata_t *cd)
+{
+ calldata_set_int(cd, "version", obs_browser_qcef_version_export());
+}
+
+void BrowserApi::create_qcef(void *, calldata_t *cd)
+{
+ proc_handler_t *ph;
+#ifdef __linux__
+ if (qApp->platformName().contains("wayland"))
+ return;
+#endif
+ ph = proc_handler_create();
+
+ proc_handler_add(ph, "void qcef_free(in ptr qcef)", &qcef_free,
+ nullptr);
+
+ proc_handler_add(
+ ph,
+ "void init_browser(in ptr qcef, out bool already_initialized)",
+ &qcef_init_browser, nullptr);
+ proc_handler_add(ph,
+ "void initialized(in ptr qcef, out bool initialized)",
+ &qcef_initialized, nullptr);
+ proc_handler_add(
+ ph,
+ "void wait_for_browser_init(in ptr qcef, out bool initialized)",
+ &qcef_wait_for_browser_init, nullptr);
+ proc_handler_add(
+ ph,
+ "void create_widget(in ptr qcef, in ptr qwidget_parent, in string url, in ptr qcef_cookie_manager, out ptr ph, out ptr qcef_widget)",
+ &qcef_create_widget, nullptr);
+ proc_handler_add(
+ ph,
+ "void create_cookie_manager(in ptr qcef, in string storage_path, in bool persist_session_cookies, out ptr ph, out ptr qcef_cookie_manager)",
+ &qcef_create_cookie_manager, nullptr);
+ proc_handler_add(
+ ph,
+ "void get_cookie_path(in ptr qcef, in string storage_path, out ptr path)",
+ &qcef_get_cookie_path, nullptr);
+ proc_handler_add(
+ ph,
+ "void add_popup_whitelist_url(in ptr qcef, in string url, in ptr qobject)",
+ &qcef_add_popup_whitelist_url, nullptr);
+ proc_handler_add(
+ ph,
+ "void add_force_popup_url(in ptr qcef, in string url, in ptr qobject)",
+ &qcef_add_force_popup_url, nullptr);
+
+ calldata_set_ptr(cd, "ph", ph);
+ calldata_set_ptr(cd, "qcef", obs_browser_create_qcef());
+}
diff --git a/obs-browser-api-impl.cpp b/obs-browser-api-impl.cpp
new file mode 100644
index 000000000..83dfa2e19
--- /dev/null
+++ b/obs-browser-api-impl.cpp
@@ -0,0 +1,44 @@
+// SPDX-FileCopyrightText: 2023 tytan652
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "obs-browser-api-impl.hpp"
+
+#include
+
+BrowserApi::BrowserApi()
+{
+ proc_handler_t *global_ph;
+
+ ph = proc_handler_create();
+ proc_handler_add(ph, "void get_api_version(out int version)",
+ &get_api_version, nullptr);
+
+#ifdef BROWSER_AVAILABLE
+ proc_handler_add(ph, "void get_qcef_version(out int version)",
+ &get_qcef_version, nullptr);
+ proc_handler_add(ph, "void create_qcef(out ptr ph, out ptr qcef)",
+ &create_qcef, nullptr);
+#endif
+
+ global_ph = obs_get_proc_handler();
+ proc_handler_add(global_ph, "void obs_browser_api_get_ph(out ptr ph)",
+ get_proc_handler, this);
+}
+
+BrowserApi::~BrowserApi()
+{
+ proc_handler_destroy(ph);
+}
+
+void BrowserApi::get_proc_handler(void *priv_data, calldata_t *cd)
+{
+ auto api = static_cast(priv_data);
+
+ calldata_set_ptr(cd, "ph", api->ph);
+}
+
+void BrowserApi::get_api_version(void *, calldata_t *cd)
+{
+ calldata_set_int(cd, "version", OBS_BROWSER_API_VERSION);
+}
diff --git a/obs-browser-api-impl.hpp b/obs-browser-api-impl.hpp
new file mode 100644
index 000000000..d80c42dcf
--- /dev/null
+++ b/obs-browser-api-impl.hpp
@@ -0,0 +1,24 @@
+// SPDX-FileCopyrightText: 2023 tytan652
+//
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include
+
+class BrowserApi {
+ proc_handler_t *ph;
+
+ static void get_proc_handler(void *priv_data, calldata_t *cd);
+
+ static void get_api_version(void *, calldata_t *cd);
+
+#ifdef BROWSER_AVAILABLE
+ static void get_qcef_version(void *, calldata_t *cd);
+ static void create_qcef(void *, calldata_t *cd);
+#endif
+
+public:
+ BrowserApi();
+ ~BrowserApi();
+};
diff --git a/obs-browser-plugin.cpp b/obs-browser-plugin.cpp
index 0278089b5..34e3e2a91 100644
--- a/obs-browser-plugin.cpp
+++ b/obs-browser-plugin.cpp
@@ -33,6 +33,7 @@
#include "browser-scheme.hpp"
#include "browser-app.hpp"
#include "browser-version.h"
+#include "obs-browser-api-impl.hpp"
#include "obs-websocket-api/obs-websocket-api.h"
#include "cef-headers.hpp"
@@ -67,6 +68,7 @@ using namespace std;
static thread manager_thread;
static bool manager_initialized = false;
os_event_t *cef_started_event = nullptr;
+static std::shared_ptr browserApi;
#if defined(_WIN32)
static int adapterCount = 0;
@@ -784,6 +786,8 @@ bool obs_module_load(void)
RegisterBrowserSource();
obs_frontend_add_event_callback(handle_obs_frontend_event, nullptr);
+ browserApi = std::make_shared();
+
#ifdef ENABLE_BROWSER_SHARED_TEXTURE
OBSDataAutoRelease private_data = obs_get_private_data();
hwaccel = obs_data_get_bool(private_data, "BrowserHWAccel");
@@ -830,6 +834,8 @@ void obs_module_post_load(void)
void obs_module_unload(void)
{
+ browserApi.reset();
+
#ifdef ENABLE_BROWSER_QT_LOOP
BrowserShutdown();
#else