diff --git a/CMakeLists.txt b/CMakeLists.txt index e534fcede..3ece8e135 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ legacy_check() option(ENABLE_BROWSER "Enable browser source plugin (required Chromium Embedded Framework)" OFF) +add_subdirectory(lib) + 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)") @@ -38,6 +40,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 @@ -46,7 +50,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/legacy.cmake b/cmake/legacy.cmake index d83719360..3bf381093 100644 --- a/cmake/legacy.cmake +++ b/cmake/legacy.cmake @@ -3,6 +3,8 @@ project(obs-browser) option(ENABLE_BROWSER "Enable building OBS with browser source plugin (required Chromium Embedded Framework)" ${OS_LINUX}) +add_subdirectory(lib) + if(NOT ENABLE_BROWSER OR NOT ENABLE_UI) message(STATUS "OBS: DISABLED obs-browser") message( @@ -54,6 +56,8 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/browser-config.h.in ${CMAKE_BINARY_DI target_sources( obs-browser PRIVATE obs-browser-plugin.cpp + obs-browser-api-impl.cpp + obs-browser-api-impl.hpp obs-browser-source.cpp obs-browser-source.hpp obs-browser-source-audio.cpp @@ -76,13 +80,13 @@ target_sources( target_include_directories(obs-browser PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/deps ${CMAKE_BINARY_DIR}/config) -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) target_compile_features(obs-browser PRIVATE cxx_std_17) -if(ENABLE_BROWSER_PANELS OR ENABLE_BROWSER_QT_LOOP) - find_qt(COMPONENTS Widgets) +find_qt(COMPONENTS Widgets) +if(ENABLE_BROWSER_PANELS OR ENABLE_BROWSER_QT_LOOP) set_target_properties( obs-browser PROPERTIES AUTOMOC ON @@ -251,6 +255,8 @@ if(ENABLE_BROWSER_PANELS) target_compile_definitions(obs-browser-panels INTERFACE BROWSER_AVAILABLE) + target_sources(obs-browser PRIVATE obs-browser-api-impl-panel.cpp obs-browser-api-impl.cpp) + if(ENABLE_BROWSER_QT_LOOP) target_compile_definitions(obs-browser-panels INTERFACE ENABLE_BROWSER_QT_LOOP) endif() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 000000000..1e9ffef04 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.22...3.25) + +legacy_check() + +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 "$" + "$") + +set_target_properties( + obs-browser-api + PROPERTIES FOLDER browser + PREFIX "" + PUBLIC_HEADER obs-browser-api.hpp) + +target_export(obs-browser-api) diff --git a/lib/cmake/legacy.cmake b/lib/cmake/legacy.cmake new file mode 100644 index 000000000..b9ea5d5d3 --- /dev/null +++ b/lib/cmake/legacy.cmake @@ -0,0 +1,22 @@ +if(POLICY CMP0090) + cmake_policy(SET CMP0090 NEW) +endif() + +project(obs-browser-api) + +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 $ + $) + +set_target_properties(obs-browser-api PROPERTIES FOLDER "browser" PUBLIC_HEADER obs-browser-api.hpp) + +export_target(obs-browser-api) diff --git a/lib/cmake/obs-browser-apiConfig.cmake.in b/lib/cmake/obs-browser-apiConfig.cmake.in new file mode 100644 index 000000000..f26978ec7 --- /dev/null +++ b/lib/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..2bd5f888a --- /dev/null +++ b/lib/obs-browser-api.hpp @@ -0,0 +1,521 @@ +// 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. + * + * PhQCefWidget are generated by PhQCef::createWidget method + */ +class PhQCefWidget : public QObject { + Q_OBJECT + + friend class PhQCef; + + 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 PhQCefWidget(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: + ~PhQCefWidget() + { + 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. + * + * PhQCefCookieManager are generated by PhQCef::createCookieManager method + */ +class PhQCefCookieManager { + friend class PhQCef; + + proc_handler_t *ph; + void *qcef_cookie_manager; + + PhQCefCookieManager(proc_handler_t *ph, void *qcef_cookie_manager) + : ph(ph), qcef_cookie_manager(qcef_cookie_manager) + { + } + +public: + ~PhQCefCookieManager() + { + 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. + * + * PhQCef are generated by PhQCef::createPhQCef method + */ +class PhQCef { + proc_handler_t *ph; + void *qcef; + + PhQCef(proc_handler_t *ph, void *qcef) : ph(ph), qcef(qcef){}; + +public: + /* Creates and return a PhQCef instance. + * + * Can return an empty shared pointer + */ + static std::shared_ptr createPhQCef() + { + 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 PhQCef(ph, qcef)); + } + + ~PhQCef() + { + 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 PhQCefWidget instance. + * + * Can return nullptr + */ + PhQCefWidget * + createWidget(QWidget *parent, const char *url, + PhQCefCookieManager *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 PhQCefWidget(w_ph, qcef_widget, parent) + : nullptr; + } + + /* Creates and return a PhQCefCookieManager 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 PhQCefCookieManager(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 188958efe..b47258fc9 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" @@ -63,6 +64,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; @@ -769,6 +771,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"); @@ -815,6 +819,8 @@ void obs_module_post_load(void) void obs_module_unload(void) { + browserApi.reset(); + #ifdef ENABLE_BROWSER_QT_LOOP BrowserShutdown(); #else