Skip to content

Commit 5b4aa9d

Browse files
committed
WebSocketApi: Implement backend for obs-websocket event listening
1 parent ee283c7 commit 5b4aa9d

File tree

5 files changed

+147
-28
lines changed

5 files changed

+147
-28
lines changed

src/WebSocketApi.cpp

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,19 @@ WebSocketApi::WebSocketApi()
4747

4848
proc_handler_add(_procHandler, "bool get_api_version(out int version)", &get_api_version, nullptr);
4949
proc_handler_add(_procHandler, "bool call_request(in string request_type, in string request_data, out ptr response)",
50-
&call_request, nullptr);
50+
&call_request, this);
51+
proc_handler_add(_procHandler, "bool register_event_callback(in ptr callback, out bool success)", &register_event_callback,
52+
this);
53+
proc_handler_add(_procHandler, "bool unregister_event_callback(in ptr callback, out bool success)",
54+
&unregister_event_callback, this);
5155
proc_handler_add(_procHandler, "bool vendor_register(in string name, out ptr vendor)", &vendor_register_cb, this);
52-
proc_handler_add(_procHandler, "bool vendor_request_register(in ptr vendor, in string type, in ptr callback)",
56+
proc_handler_add(_procHandler,
57+
"bool vendor_request_register(in ptr vendor, in string type, in ptr callback, out bool success)",
5358
&vendor_request_register_cb, this);
54-
proc_handler_add(_procHandler, "bool vendor_request_unregister(in ptr vendor, in string type)",
59+
proc_handler_add(_procHandler, "bool vendor_request_unregister(in ptr vendor, in string type, out bool success)",
5560
&vendor_request_unregister_cb, this);
56-
proc_handler_add(_procHandler, "bool vendor_event_emit(in ptr vendor, in string type, in ptr data)", &vendor_event_emit_cb,
57-
this);
61+
proc_handler_add(_procHandler, "bool vendor_event_emit(in ptr vendor, in string type, in ptr data, out bool success)",
62+
&vendor_event_emit_cb, this);
5863

5964
proc_handler_t *ph = obs_get_proc_handler();
6065
assert(ph != NULL);
@@ -70,6 +75,10 @@ WebSocketApi::~WebSocketApi()
7075

7176
proc_handler_destroy(_procHandler);
7277

78+
size_t numEventCallbacks = _eventCallbacks.size();
79+
_eventCallbacks.clear();
80+
blog_debug("[WebSocketApi::~WebSocketApi] Deleted %ld event callbacks", numEventCallbacks);
81+
7382
for (auto vendor : _vendors) {
7483
blog_debug("[WebSocketApi::~WebSocketApi] Deleting vendor: %s", vendor.first.c_str());
7584
delete vendor.second;
@@ -80,10 +89,19 @@ WebSocketApi::~WebSocketApi()
8089

8190
void WebSocketApi::BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData, uint8_t rpcVersion)
8291
{
83-
UNUSED_PARAMETER(requiredIntent);
84-
UNUSED_PARAMETER(eventType);
85-
UNUSED_PARAMETER(eventData);
86-
UNUSED_PARAMETER(rpcVersion);
92+
if (!_obsReady)
93+
return;
94+
95+
// Only broadcast events applicable to the latest RPC version
96+
if (rpcVersion && rpcVersion != CURRENT_RPC_VERSION)
97+
return;
98+
99+
std::string eventDataString = eventData.dump();
100+
101+
std::shared_lock l(_mutex);
102+
103+
for (auto &cb : _eventCallbacks)
104+
cb.callback(requiredIntent, eventType.c_str(), eventDataString.c_str(), cb.priv_data);
87105
}
88106

89107
enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::string vendorName, std::string requestType,
@@ -128,14 +146,27 @@ void WebSocketApi::get_api_version(void *, calldata_t *cd)
128146
RETURN_SUCCESS();
129147
}
130148

131-
void WebSocketApi::call_request(void *, calldata_t *cd)
149+
void WebSocketApi::call_request(void *priv_data, calldata_t *cd)
132150
{
151+
auto c = static_cast<WebSocketApi *>(priv_data);
152+
153+
#if !defined(PLUGIN_TESTS)
154+
if (!c->_obsReady)
155+
RETURN_FAILURE();
156+
#endif
157+
133158
const char *request_type = calldata_string(cd, "request_type");
134159
const char *request_data = calldata_string(cd, "request_data");
135160

136161
if (!request_type)
137162
RETURN_FAILURE();
138163

164+
#ifdef PLUGIN_TESTS
165+
// Allow plugin tests to complete, even though OBS wouldn't be ready at the time of the test
166+
if (!c->_obsReady && std::string(request_type) != "GetVersion")
167+
RETURN_FAILURE();
168+
#endif
169+
139170
auto response = static_cast<obs_websocket_request_response *>(bzalloc(sizeof(struct obs_websocket_request_response)));
140171
if (!response)
141172
RETURN_FAILURE();
@@ -164,6 +195,52 @@ void WebSocketApi::call_request(void *, calldata_t *cd)
164195
RETURN_SUCCESS();
165196
}
166197

198+
void WebSocketApi::register_event_callback(void *priv_data, calldata_t *cd)
199+
{
200+
auto c = static_cast<WebSocketApi *>(priv_data);
201+
202+
void *voidCallback;
203+
if (!calldata_get_ptr(cd, "callback", &voidCallback) || !voidCallback) {
204+
blog(LOG_WARNING, "[WebSocketApi::register_event_callback] Failed due to missing `callback` pointer.");
205+
RETURN_FAILURE();
206+
}
207+
208+
auto cb = static_cast<obs_websocket_event_callback *>(voidCallback);
209+
210+
std::unique_lock l(c->_mutex);
211+
212+
int64_t foundIndex = c->GetEventCallbackIndex(*cb);
213+
if (foundIndex != -1)
214+
RETURN_FAILURE();
215+
216+
c->_eventCallbacks.push_back(*cb);
217+
218+
RETURN_SUCCESS();
219+
}
220+
221+
void WebSocketApi::unregister_event_callback(void *priv_data, calldata_t *cd)
222+
{
223+
auto c = static_cast<WebSocketApi *>(priv_data);
224+
225+
void *voidCallback;
226+
if (!calldata_get_ptr(cd, "callback", &voidCallback) || !voidCallback) {
227+
blog(LOG_WARNING, "[WebSocketApi::register_event_callback] Failed due to missing `callback` pointer.");
228+
RETURN_FAILURE();
229+
}
230+
231+
auto cb = static_cast<obs_websocket_event_callback *>(voidCallback);
232+
233+
std::unique_lock l(c->_mutex);
234+
235+
int64_t foundIndex = c->GetEventCallbackIndex(*cb);
236+
if (foundIndex == -1)
237+
RETURN_FAILURE();
238+
239+
c->_eventCallbacks.erase(c->_eventCallbacks.begin() + foundIndex);
240+
241+
RETURN_SUCCESS();
242+
}
243+
167244
void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
168245
{
169246
auto c = static_cast<WebSocketApi *>(priv_data);
@@ -174,7 +251,7 @@ void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
174251
RETURN_FAILURE();
175252
}
176253

177-
// Theoretically doesn't need a mutex, but it's good to be safe.
254+
// Theoretically doesn't need a mutex due to module load being single-thread, but it's good to be safe.
178255
std::unique_lock l(c->_mutex);
179256

180257
if (c->_vendors.count(vendorName)) {

src/WebSocketApi.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,22 @@ class WebSocketApi {
5757
inline void SetVendorEventCallback(VendorEventCallback cb) { _vendorEventCallback = cb; }
5858

5959
private:
60+
inline int64_t GetEventCallbackIndex(obs_websocket_event_callback &cb)
61+
{
62+
for (int64_t i = 0; i < (int64_t)_eventCallbacks.size(); i++) {
63+
auto currentCb = _eventCallbacks[i];
64+
if (currentCb.callback == cb.callback && currentCb.priv_data == cb.priv_data)
65+
return i;
66+
}
67+
return -1;
68+
}
69+
70+
// Proc handlers
6071
static void get_ph_cb(void *priv_data, calldata_t *cd);
6172
static void get_api_version(void *, calldata_t *cd);
6273
static void call_request(void *, calldata_t *cd);
74+
static void register_event_callback(void *, calldata_t *cd);
75+
static void unregister_event_callback(void *, calldata_t *cd);
6376
static void vendor_register_cb(void *priv_data, calldata_t *cd);
6477
static void vendor_request_register_cb(void *priv_data, calldata_t *cd);
6578
static void vendor_request_unregister_cb(void *priv_data, calldata_t *cd);
@@ -68,6 +81,7 @@ class WebSocketApi {
6881
std::shared_mutex _mutex;
6982
proc_handler_t *_procHandler;
7083
std::map<std::string, Vendor *> _vendors;
84+
std::vector<obs_websocket_event_callback> _eventCallbacks;
7185

7286
std::atomic<bool> _obsReady = false;
7387

src/obs-websocket.cpp

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,16 @@ bool obs_module_load(void)
103103
}
104104

105105
#ifdef PLUGIN_TESTS
106+
void test_call_request();
107+
void test_register_event_callback();
106108
void test_register_vendor();
107109
#endif
108110

109111
void obs_module_post_load(void)
110112
{
111113
#ifdef PLUGIN_TESTS
114+
test_call_request();
115+
test_register_event_callback();
112116
test_register_vendor();
113117
#endif
114118

@@ -229,12 +233,43 @@ void OnObsReady(bool ready)
229233
}
230234

231235
#ifdef PLUGIN_TESTS
236+
void test_call_request()
237+
{
238+
blog(LOG_INFO, "[test_call_request] Testing obs-websocket plugin API request calling...");
232239

233-
static void test_vendor_request_cb(obs_data_t *requestData, obs_data_t *responseData, void *priv_data)
240+
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
241+
if (response) {
242+
blog(LOG_INFO, "[test_call_request] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
243+
response->status_code, response->comment, response->response_data);
244+
obs_websocket_request_response_free(response);
245+
} else {
246+
blog(LOG_ERROR, "[test_call_request] Failed to call GetVersion request via obs-websocket plugin API!");
247+
}
248+
249+
blog(LOG_INFO, "[test_call_request] Test done.");
250+
}
251+
252+
static void test_event_cb(uint64_t eventIntent, const char *eventType, const char *eventData, void *priv_data)
253+
{
254+
blog(LOG_DEBUG, "[test_event_cb] New event! Type: %s | Data: %s", eventType, eventData);
255+
256+
UNUSED_PARAMETER(eventIntent);
257+
UNUSED_PARAMETER(priv_data);
258+
}
259+
260+
void test_register_event_callback()
234261
{
235-
blog(LOG_INFO, "[test_vendor_request_cb] Request called!");
262+
blog(LOG_INFO, "[test_register_event_callback] Registering test event callback...");
263+
264+
if (!obs_websocket_register_event_callback(test_event_cb, nullptr))
265+
blog(LOG_ERROR, "[test_register_event_callback] Failed to register event callback!");
236266

237-
blog(LOG_INFO, "[test_vendor_request_cb] Request data: %s", obs_data_get_json(requestData));
267+
blog(LOG_INFO, "[test_register_event_callback] Test done.");
268+
}
269+
270+
static void test_vendor_request_cb(obs_data_t *requestData, obs_data_t *responseData, void *priv_data)
271+
{
272+
blog(LOG_INFO, "[test_vendor_request_cb] Request called! Request data: %s", obs_data_get_json(requestData));
238273

239274
// Set an item to the response data
240275
obs_data_set_string(responseData, "test", "pp");
@@ -245,34 +280,25 @@ static void test_vendor_request_cb(obs_data_t *requestData, obs_data_t *response
245280

246281
void test_register_vendor()
247282
{
248-
blog(LOG_INFO, "[test_register_vendor] Registering test vendor...");
283+
blog(LOG_INFO, "[test_register_vendor] Testing vendor registration...");
249284

250285
// Test plugin API version fetch
251286
uint apiVersion = obs_websocket_get_api_version();
252287
blog(LOG_INFO, "[test_register_vendor] obs-websocket plugin API version: %u", apiVersion);
253288

254-
// Test calling obs-websocket requests
255-
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion");
256-
if (response) {
257-
blog(LOG_INFO, "[test_register_vendor] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
258-
response->status_code, response->comment, response->response_data);
259-
obs_websocket_request_response_free(response);
260-
}
261-
262289
// Test vendor creation
263290
auto vendor = obs_websocket_register_vendor("obs-websocket-test");
264291
if (!vendor) {
265-
blog(LOG_WARNING, "[test_register_vendor] Failed to create vendor!");
292+
blog(LOG_ERROR, "[test_register_vendor] Failed to create vendor!");
266293
return;
267294
}
268295

269296
// Test vendor request registration
270297
if (!obs_websocket_vendor_register_request(vendor, "TestRequest", test_vendor_request_cb, vendor)) {
271-
blog(LOG_WARNING, "[test_register_vendor] Failed to register vendor request!");
298+
blog(LOG_ERROR, "[test_register_vendor] Failed to register vendor request!");
272299
return;
273300
}
274301

275-
blog(LOG_INFO, "[test_register_vendor] Post load completed.");
302+
blog(LOG_INFO, "[test_register_vendor] Test done.");
276303
}
277-
278304
#endif

src/obs-websocket.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
2626
#include "utils/Obs.h"
2727
#include "plugin-macros.generated.h"
2828

29+
#define CURRENT_RPC_VERSION 1
30+
2931
struct Config;
3032
typedef std::shared_ptr<Config> ConfigPtr;
3133

src/websocketserver/WebSocketServer_Protocol.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
3131

3232
static bool IsSupportedRpcVersion(uint8_t requestedVersion)
3333
{
34-
return (requestedVersion == 1);
34+
return (requestedVersion == CURRENT_RPC_VERSION);
3535
}
3636

3737
static json ConstructRequestResult(RequestResult requestResult, const json &requestJson)

0 commit comments

Comments
 (0)