diff --git a/CMakeLists.txt b/CMakeLists.txt index 18349b90..fd742099 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,6 +259,8 @@ target_sources( lib/utils/utility.hpp lib/utils/volume-control.cpp lib/utils/volume-control.hpp + lib/utils/websocket-api.cpp + lib/utils/websocket-api.hpp lib/variables/variable-line-edit.cpp lib/variables/variable-line-edit.hpp lib/variables/variable-number.hpp diff --git a/deps/obs-websocket/lib/obs-websocket-api.h b/deps/obs-websocket/lib/obs-websocket-api.h index 3ab1a623..c8fa52ef 100644 --- a/deps/obs-websocket/lib/obs-websocket-api.h +++ b/deps/obs-websocket/lib/obs-websocket-api.h @@ -29,7 +29,8 @@ extern "C" { #endif typedef void *obs_websocket_vendor; -typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *); +typedef void (*obs_websocket_request_callback_function)(obs_data_t *, + obs_data_t *, void *); struct obs_websocket_request_response { unsigned int status_code; @@ -44,7 +45,7 @@ struct obs_websocket_request_callback { void *priv_data; }; -inline proc_handler_t *_ph; +static proc_handler_t *_ph; /* ==================== INTERNAL API FUNCTIONS ==================== */ @@ -53,9 +54,10 @@ static inline proc_handler_t *obs_websocket_get_ph(void) proc_handler_t *global_ph = obs_get_proc_handler(); assert(global_ph != NULL); - calldata_t cd = {0}; + calldata_t cd = {0, 0, 0, 0}; if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd)) - blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?"); + blog(LOG_DEBUG, + "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?"); proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph"); calldata_free(&cd); @@ -69,7 +71,9 @@ static inline bool obs_websocket_ensure_ph(void) return _ph != NULL; } -static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd) +static inline bool +obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, + const char *proc_name, calldata_t *cd) { if (!obs_websocket_ensure_ph()) return false; @@ -91,12 +95,12 @@ static inline unsigned int obs_websocket_get_api_version(void) if (!obs_websocket_ensure_ph()) return 0; - calldata_t cd = {0}; + calldata_t cd = {0, 0, 0, 0}; if (!proc_handler_call(_ph, "get_api_version", &cd)) return 1; // API v1 does not include get_api_version - unsigned int ret = calldata_int(&cd, "version"); + unsigned int ret = (unsigned int)calldata_int(&cd, "version"); calldata_free(&cd); @@ -104,7 +108,12 @@ static inline unsigned int obs_websocket_get_api_version(void) } // Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()` -static inline obs_websocket_request_response *obs_websocket_call_request(const char *request_type, obs_data_t *request_data = NULL) +static inline struct obs_websocket_request_response * +obs_websocket_call_request(const char *request_type, obs_data_t *request_data +#ifdef __cplusplus + = NULL +#endif +) { if (!obs_websocket_ensure_ph()) return NULL; @@ -113,14 +122,16 @@ static inline obs_websocket_request_response *obs_websocket_call_request(const c if (request_data) request_data_string = obs_data_get_json(request_data); - calldata_t cd = {0}; + calldata_t cd = {0, 0, 0, 0}; calldata_set_string(&cd, "request_type", request_type); calldata_set_string(&cd, "request_data", request_data_string); proc_handler_call(_ph, "call_request", &cd); - auto ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response"); + struct obs_websocket_request_response *ret = + (struct obs_websocket_request_response *)calldata_ptr( + &cd, "response"); calldata_free(&cd); @@ -128,7 +139,8 @@ static inline obs_websocket_request_response *obs_websocket_call_request(const c } // Free a request response object returned by `obs_websocket_call_request()` -static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response) +static inline void obs_websocket_request_response_free( + struct obs_websocket_request_response *response) { if (!response) return; @@ -144,12 +156,13 @@ static inline void obs_websocket_request_response_free(struct obs_websocket_requ // ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK! // Registers a new "vendor" (Example: obs-ndi) -static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name) +static inline obs_websocket_vendor +obs_websocket_register_vendor(const char *vendor_name) { if (!obs_websocket_ensure_ph()) return NULL; - calldata_t cd = {0}; + calldata_t cd = {0, 0, 0, 0}; calldata_set_string(&cd, "name", vendor_name); @@ -161,32 +174,37 @@ static inline obs_websocket_vendor obs_websocket_register_vendor(const char *ven } // Registers a new request for a vendor -static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type, - obs_websocket_request_callback_function request_callback, void *priv_data) +static inline bool obs_websocket_vendor_register_request( + obs_websocket_vendor vendor, const char *request_type, + obs_websocket_request_callback_function request_callback, + void *priv_data) { - calldata_t cd = {0}; + calldata_t cd = {0, 0, 0, 0}; - struct obs_websocket_request_callback cb = {}; - cb.callback = request_callback; - cb.priv_data = priv_data; + struct obs_websocket_request_callback cb = {request_callback, + priv_data}; calldata_set_string(&cd, "type", request_type); calldata_set_ptr(&cd, "callback", &cb); - bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd); + bool success = obs_websocket_vendor_run_simple_proc( + vendor, "vendor_request_register", &cd); calldata_free(&cd); return success; } // Unregisters an existing vendor request -static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type) +static inline bool +obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, + const char *request_type) { - calldata_t cd = {0}; + calldata_t cd = {0, 0, 0, 0}; calldata_set_string(&cd, "type", request_type); - bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd); + bool success = obs_websocket_vendor_run_simple_proc( + vendor, "vendor_request_unregister", &cd); calldata_free(&cd); return success; @@ -194,14 +212,17 @@ static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor // Does not affect event_data refcount. // Emits an event under the vendor's name -static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data) +static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, + const char *event_name, + obs_data_t *event_data) { - calldata_t cd = {0}; + calldata_t cd = {0, 0, 0, 0}; calldata_set_string(&cd, "type", event_name); calldata_set_ptr(&cd, "data", (void *)event_data); - bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd); + bool success = obs_websocket_vendor_run_simple_proc( + vendor, "vendor_event_emit", &cd); calldata_free(&cd); return success; diff --git a/lib/advanced-scene-switcher.cpp b/lib/advanced-scene-switcher.cpp index 74278ffb..148f66a8 100644 --- a/lib/advanced-scene-switcher.cpp +++ b/lib/advanced-scene-switcher.cpp @@ -14,6 +14,7 @@ #include "tab-helpers.hpp" #include "utility.hpp" #include "version.h" +#include "websocket-api.hpp" #include #include @@ -454,6 +455,8 @@ void SwitcherData::Start() // Will be overwritten quickly but might be useful writeToStatusFile("Advanced Scene Switcher running"); + SendWebsocketVendorEvent("AdvancedSceneSwitcherStarted", + nullptr); } if (showSystemTrayNotifications) { @@ -481,6 +484,10 @@ void SwitcherData::Stop() delete th; th = nullptr; writeToStatusFile("Advanced Scene Switcher stopped"); + if (!obsIsShuttingDown) { + SendWebsocketVendorEvent("AdvancedSceneSwitcherStopped", + nullptr); + } } if (showSystemTrayNotifications) { diff --git a/lib/utils/websocket-api.cpp b/lib/utils/websocket-api.cpp new file mode 100644 index 00000000..9ef4cace --- /dev/null +++ b/lib/utils/websocket-api.cpp @@ -0,0 +1,114 @@ +#include "websocket-api.hpp" +#include "log-helper.hpp" +#include "obs-websocket-api.h" +#include "plugin-state-helpers.hpp" + +#include + +namespace advss { + +static std::vector> callbacks; + +constexpr char VendorName[] = "AdvancedSceneSwitcher"; +constexpr char VendorRequestStart[] = "AdvancedSceneSwitcherStart"; +constexpr char VendorRequestStop[] = "AdvancedSceneSwitcherStop"; +constexpr char VendorRequestStatus[] = "IsAdvancedSceneSwitcherRunning"; +obs_websocket_vendor vendor; + +static void registerWebsocketVendor(); + +static bool setup(); +static bool setupDone = setup(); + +bool setup() +{ + AddPluginPostLoadStep(registerWebsocketVendor); + return true; +} + +static void +registerWebsocketVendorRequest(const char *requestName, + obs_websocket_request_callback_function callback) +{ + if (!obs_websocket_vendor_register_request(vendor, requestName, + callback, NULL)) { + blog(LOG_ERROR, + "Failed to register \"%s\" request with obs-websocket.", + requestName); + } +} + +static void registerWebsocketVendor() +{ + vendor = obs_websocket_register_vendor(VendorName); + if (!vendor) { + blog(LOG_ERROR, + "Vendor registration failed! (obs-websocket should have logged something if installed properly.)"); + return; + } + + auto api_version = obs_websocket_get_api_version(); + if (api_version == 0) { + blog(LOG_ERROR, + "Unable to fetch obs-websocket plugin API version."); + return; + } else if (api_version == 1) { + blog(LOG_WARNING, + "Unsupported obs-websocket plugin API version for calling requests."); + return; + } + + registerWebsocketVendorRequest(VendorRequestStart, + [](obs_data_t *, obs_data_t *, void *) { + StartPlugin(); + }); + registerWebsocketVendorRequest(VendorRequestStop, + [](obs_data_t *, obs_data_t *, void *) { + StopPlugin(); + }); + registerWebsocketVendorRequest( + VendorRequestStatus, + [](obs_data_t *, obs_data_t *response, void *) { + obs_data_set_bool(response, "isRunning", + PluginIsRunning()); + }); +} + +const char *GetWebsocketVendorName() +{ + return VendorName; +} + +void RegisterWebsocketRequest( + const std::string &name, + const std::function &callback) +{ + static std::mutex m; + std::lock_guard lock(m); + + callbacks.emplace_back(callback); + auto handleRequest = [](obs_data *requestData, obs_data *other, + void *cbPtr) { + auto cb = *(static_cast< + std::function *>( + cbPtr)); + cb(requestData, other); + }; + + AddPluginPostLoadStep([name, handleRequest]() { + if (!obs_websocket_vendor_register_request(vendor, name.c_str(), + handleRequest, + &callbacks.back())) { + blog(LOG_ERROR, + "Failed to register \"%s\" request with obs-websocket.", + name.c_str()); + } + }); +} + +void SendWebsocketVendorEvent(const std::string &eventName, obs_data_t *data) +{ + obs_websocket_vendor_emit_event(vendor, eventName.c_str(), data); +} + +} // namespace advss diff --git a/lib/utils/websocket-api.hpp b/lib/utils/websocket-api.hpp new file mode 100644 index 00000000..56754331 --- /dev/null +++ b/lib/utils/websocket-api.hpp @@ -0,0 +1,17 @@ +#pragma once +#include "export-symbol-helper.hpp" + +#include +#include +#include + +namespace advss { + +EXPORT const char *GetWebsocketVendorName(); +EXPORT void RegisterWebsocketRequest( + const std::string &name, + const std::function &callback); +EXPORT void SendWebsocketVendorEvent(const std::string &eventName, + obs_data_t *data); + +} // namespace advss diff --git a/plugins/base/utils/websocket-helpers.cpp b/plugins/base/utils/websocket-helpers.cpp index 4a1cc530..62870aeb 100644 --- a/plugins/base/utils/websocket-helpers.cpp +++ b/plugins/base/utils/websocket-helpers.cpp @@ -3,6 +3,7 @@ #include "log-helper.hpp" #include "plugin-state-helpers.hpp" #include "sync-helpers.hpp" +#include "websocket-api.hpp" #include #include @@ -16,20 +17,18 @@ using websocketpp::lib::bind; #define RPC_VERSION 1 #undef DispatchMessage -constexpr char VendorName[] = "AdvancedSceneSwitcher"; -constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage"; -constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent"; -obs_websocket_vendor vendor; +constexpr char VendorRequestMessage[] = "AdvancedSceneSwitcherMessage"; +constexpr char VendorEventMessage[] = "AdvancedSceneSwitcherEvent"; static WebsocketMessageDispatcher websocketMessageDispatcher; -static void registerWebsocketVendor(); static bool setup(); static bool setupDone = setup(); +static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *); bool setup() { - AddPluginPostLoadStep(registerWebsocketVendor); + RegisterWebsocketRequest(VendorRequestMessage, receiveWebsocketMessage); return true; } @@ -40,14 +39,12 @@ WebsocketMessageBuffer RegisterForWebsocketMessages() void SendWebsocketEvent(const std::string &eventMsg) { - auto data = obs_data_create(); + OBSDataAutoRelease data = obs_data_create(); obs_data_set_string(data, "message", eventMsg.c_str()); - obs_websocket_vendor_emit_event(vendor, VendorEvent, data); - obs_data_release(data); + SendWebsocketVendorEvent(VendorEventMessage, data); } -static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *, - void *) +static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *) { if (!obs_data_has_user_value(request_data, "message")) { vblog(LOG_INFO, "received unexpected m '%s'", @@ -60,32 +57,6 @@ static void receiveWebsocketMessage(obs_data_t *request_data, obs_data_t *, vblog(LOG_INFO, "received message: %s", msg); } -static void registerWebsocketVendor() -{ - vendor = obs_websocket_register_vendor(VendorName); - if (!vendor) { - blog(LOG_ERROR, - "Vendor registration failed! (obs-websocket should have logged something if installed properly.)"); - return; - } - - if (!obs_websocket_vendor_register_request( - vendor, VendorRequest, receiveWebsocketMessage, NULL)) - blog(LOG_ERROR, - "Failed to register `AdvancedSceneSwitcherMessage` request with obs-websocket."); - - uint api_version = obs_websocket_get_api_version(); - if (api_version == 0) { - blog(LOG_ERROR, - "Unable to fetch obs-websocket plugin API version."); - return; - } else if (api_version == 1) { - blog(LOG_WARNING, - "Unsupported obs-websocket plugin API version for calling requests."); - return; - } -} - WSClientConnection::WSClientConnection(bool useOBSProtocol) : QObject(nullptr) { _client.get_alog().clear_channels( @@ -199,8 +170,8 @@ std::string ConstructVendorRequestMessage(const std::string &message) obs_data_set_string(data, "requestId", message.c_str()); auto vendorData = obs_data_create(); - obs_data_set_string(vendorData, "vendorName", VendorName); - obs_data_set_string(vendorData, "requestType", VendorRequest); + obs_data_set_string(vendorData, "vendorName", GetWebsocketVendorName()); + obs_data_set_string(vendorData, "requestType", VendorRequestMessage); auto msgObj = obs_data_create(); obs_data_set_string(msgObj, "message", message.c_str()); @@ -298,14 +269,14 @@ void WSClientConnection::HandleEvent(obs_data_t *msg) { auto d = obs_data_get_obj(msg, "d"); auto eventData = obs_data_get_obj(d, "eventData"); - if (strcmp(obs_data_get_string(eventData, "vendorName"), VendorName) != - 0) { + if (strcmp(obs_data_get_string(eventData, "vendorName"), + GetWebsocketVendorName()) != 0) { vblog(LOG_INFO, "ignoring vendor event from \"%s\"", obs_data_get_string(eventData, "vendorName")); return; } - if (strcmp(obs_data_get_string(eventData, "eventType"), VendorEvent) != - 0) { + if (strcmp(obs_data_get_string(eventData, "eventType"), + VendorEventMessage) != 0) { vblog(LOG_INFO, "ignoring event type\"%s\"", obs_data_get_string(eventData, "eventType")); return;