base: Format code

This commit is contained in:
tt2468 2022-05-13 21:17:17 -07:00
parent 7b238793d0
commit f73e78582b
76 changed files with 4673 additions and 2912 deletions

View File

@ -1,78 +1,89 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com> Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
// Similar example code can be found in ../../src/obs-websocket.cpp // Similar example code can be found in ../../src/obs-websocket.cpp
// You can test that sample code by specifying -DPLUGIN_TESTS=TRUE // You can test that sample code by specifying -DPLUGIN_TESTS=TRUE
#include <obs-module.h> #include <obs-module.h>
#include "../obs-websocket-api.h" #include "../obs-websocket-api.h"
OBS_DECLARE_MODULE() OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US") OBS_MODULE_USE_DEFAULT_LOCALE(PLUGIN_NAME, "en-US")
obs_websocket_vendor vendor; obs_websocket_vendor vendor;
bool obs_module_load(void) bool obs_module_load(void)
{ {
blog(LOG_INFO, "Example obs-websocket-api plugin loaded!"); blog(LOG_INFO, "Example obs-websocket-api plugin loaded!");
return true; return true;
} }
void example_request_cb(obs_data_t *request_data, obs_data_t *response_data, void *priv_data); void example_request_cb(obs_data_t *request_data, obs_data_t *response_data,
void obs_module_post_load(void) void *priv_data);
{ void obs_module_post_load(void)
vendor = obs_websocket_register_vendor("api_example_plugin"); {
if (!vendor) { vendor = obs_websocket_register_vendor("api_example_plugin");
blog(LOG_ERROR, "Vendor registration failed! (obs-websocket should have logged something if installed properly.)"); if (!vendor) {
return; blog(LOG_ERROR,
} "Vendor registration failed! (obs-websocket should have logged something if installed properly.)");
return;
if (!obs_websocket_vendor_register_request(vendor, "example_request", example_request_cb, NULL)) }
blog(LOG_ERROR, "Failed to register `example_request` request with obs-websocket.");
if (!obs_websocket_vendor_register_request(vendor, "example_request",
uint api_version = obs_websocket_get_api_version(); example_request_cb, NULL))
if (api_version == 0) { blog(LOG_ERROR,
blog(LOG_ERROR, "Unable to fetch obs-websocket plugin API version."); "Failed to register `example_request` request with obs-websocket.");
return;
} else if (api_version == 1) { uint api_version = obs_websocket_get_api_version();
blog(LOG_WARNING, "Unsupported obs-websocket plugin API version for calling requests."); if (api_version == 0) {
return; blog(LOG_ERROR,
} "Unable to fetch obs-websocket plugin API version.");
return;
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion"); } else if (api_version == 1) {
if (!response) { blog(LOG_WARNING,
blog(LOG_ERROR, "Failed to call GetVersion due to obs-websocket not being installed."); "Unsupported obs-websocket plugin API version for calling requests.");
return; return;
} }
blog(LOG_INFO, "[obs_module_post_load] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s", response->status_code, response->comment, response->response_data);
obs_websocket_request_response_free(response); struct obs_websocket_request_response *response =
} obs_websocket_call_request("GetVersion");
if (!response) {
void obs_module_unload(void) blog(LOG_ERROR,
{ "Failed to call GetVersion due to obs-websocket not being installed.");
blog(LOG_INFO, "Example obs-websocket-api plugin unloaded!"); return;
} }
blog(LOG_INFO,
void example_request_cb(obs_data_t *request_data, obs_data_t *response_data, void *priv_data) "[obs_module_post_load] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
{ response->status_code, response->comment, response->response_data);
if (obs_data_has_user_value(request_data, "ping")) obs_websocket_request_response_free(response);
obs_data_set_bool(response_data, "pong", true); }
UNUSED_PARAMETER(priv_data); void obs_module_unload(void)
} {
blog(LOG_INFO, "Example obs-websocket-api plugin unloaded!");
}
void example_request_cb(obs_data_t *request_data, obs_data_t *response_data,
void *priv_data)
{
if (obs_data_has_user_value(request_data, "ping"))
obs_data_set_bool(response_data, "pong", true);
UNUSED_PARAMETER(priv_data);
}

View File

@ -28,8 +28,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
extern "C" { extern "C" {
#endif #endif
typedef void* obs_websocket_vendor; 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 { struct obs_websocket_request_response {
unsigned int status_code; unsigned int status_code;
@ -55,8 +56,9 @@ static inline proc_handler_t *obs_websocket_get_ph(void)
calldata_t cd = {0}; calldata_t cd = {0};
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd)) 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,
proc_handler_t *ret = (proc_handler_t*)calldata_ptr(&cd, "ph"); "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); calldata_free(&cd);
return ret; return ret;
@ -69,7 +71,9 @@ static inline bool obs_websocket_ensure_ph(void)
return _ph != NULL; 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()) if (!obs_websocket_ensure_ph())
return false; return false;
@ -104,7 +108,9 @@ static inline unsigned int obs_websocket_get_api_version(void)
} }
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()` // 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 obs_websocket_request_response *
obs_websocket_call_request(const char *request_type,
obs_data_t *request_data = NULL)
{ {
if (!obs_websocket_ensure_ph()) if (!obs_websocket_ensure_ph())
return NULL; return NULL;
@ -120,7 +126,8 @@ static inline obs_websocket_request_response *obs_websocket_call_request(const c
proc_handler_call(_ph, "call_request", &cd); proc_handler_call(_ph, "call_request", &cd);
auto ret = (struct obs_websocket_request_response*)calldata_ptr(&cd, "response"); auto ret = (struct obs_websocket_request_response *)calldata_ptr(
&cd, "response");
calldata_free(&cd); calldata_free(&cd);
@ -128,7 +135,8 @@ static inline obs_websocket_request_response *obs_websocket_call_request(const c
} }
// Free a request response object returned by `obs_websocket_call_request()` // 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) if (!response)
return; return;
@ -144,7 +152,8 @@ static inline void obs_websocket_request_response_free(struct obs_websocket_requ
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK! // ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
// Registers a new "vendor" (Example: obs-ndi) // 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()) if (!obs_websocket_ensure_ph())
return NULL; return NULL;
@ -161,7 +170,10 @@ static inline obs_websocket_vendor obs_websocket_register_vendor(const char *ven
} }
// Registers a new request for a vendor // 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};
@ -172,20 +184,24 @@ static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor ve
calldata_set_string(&cd, "type", request_type); calldata_set_string(&cd, "type", request_type);
calldata_set_ptr(&cd, "callback", &cb); 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); calldata_free(&cd);
return success; return success;
} }
// Unregisters an existing vendor request // 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};
calldata_set_string(&cd, "type", request_type); 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); calldata_free(&cd);
return success; return success;
@ -193,14 +209,17 @@ static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor
// Does not affect event_data refcount. // Does not affect event_data refcount.
// Emits an event under the vendor's name // 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};
calldata_set_string(&cd, "type", event_name); calldata_set_string(&cd, "type", event_name);
calldata_set_ptr(&cd, "data", (void*)event_data); 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); calldata_free(&cd);
return success; return success;

View File

@ -38,37 +38,44 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define CMDLINE_WEBSOCKET_PASSWORD "websocket_password" #define CMDLINE_WEBSOCKET_PASSWORD "websocket_password"
#define CMDLINE_WEBSOCKET_DEBUG "websocket_debug" #define CMDLINE_WEBSOCKET_DEBUG "websocket_debug"
Config::Config() : Config::Config()
PortOverridden(false), : PortOverridden(false),
PasswordOverridden(false), PasswordOverridden(false),
FirstLoad(true), FirstLoad(true),
ServerEnabled(true), ServerEnabled(true),
ServerPort(4455), ServerPort(4455),
BindLoopback(true), BindLoopback(true),
Ipv4Only(false), Ipv4Only(false),
DebugEnabled(false), DebugEnabled(false),
AlertsEnabled(false), AlertsEnabled(false),
AuthRequired(true), AuthRequired(true),
ServerPassword("") ServerPassword("")
{ {
SetDefaultsToGlobalStore(); SetDefaultsToGlobalStore();
} }
void Config::Load() void Config::Load()
{ {
config_t* obsConfig = GetConfigStore(); config_t *obsConfig = GetConfigStore();
if (!obsConfig) { if (!obsConfig) {
blog(LOG_ERROR, "[Config::Load] Unable to fetch OBS config!"); blog(LOG_ERROR, "[Config::Load] Unable to fetch OBS config!");
return; return;
} }
FirstLoad = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD); FirstLoad = config_get_bool(obsConfig, CONFIG_SECTION_NAME,
ServerEnabled = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED); PARAM_FIRSTLOAD);
AlertsEnabled = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS); ServerEnabled =
ServerPort = config_get_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT); config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED);
BindLoopback = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_BINDLOOPBACK); AlertsEnabled =
AuthRequired = config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED); config_get_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS);
ServerPassword = config_get_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD); ServerPort =
config_get_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT);
BindLoopback = config_get_bool(obsConfig, CONFIG_SECTION_NAME,
PARAM_BINDLOOPBACK);
AuthRequired = config_get_bool(obsConfig, CONFIG_SECTION_NAME,
PARAM_AUTHREQUIRED);
ServerPassword = config_get_string(obsConfig, CONFIG_SECTION_NAME,
PARAM_PASSWORD);
// Set server password and save it to the config before processing overrides, // Set server password and save it to the config before processing overrides,
// so that there is always a true configured password regardless of if // so that there is always a true configured password regardless of if
@ -76,38 +83,49 @@ void Config::Load()
if (FirstLoad) { if (FirstLoad) {
FirstLoad = false; FirstLoad = false;
if (ServerPassword.isEmpty()) { if (ServerPassword.isEmpty()) {
blog(LOG_INFO, "[Config::Load] (FirstLoad) Generating new server password."); blog(LOG_INFO,
ServerPassword = QString::fromStdString(Utils::Crypto::GeneratePassword()); "[Config::Load] (FirstLoad) Generating new server password.");
ServerPassword = QString::fromStdString(
Utils::Crypto::GeneratePassword());
} else { } else {
blog(LOG_INFO, "[Config::Load] (FirstLoad) Not generating new password since one is already configured."); blog(LOG_INFO,
"[Config::Load] (FirstLoad) Not generating new password since one is already configured.");
} }
Save(); Save();
} }
// Process `--websocket_port` override // Process `--websocket_port` override
QString portArgument = Utils::Platform::GetCommandLineArgument(CMDLINE_WEBSOCKET_PORT); QString portArgument =
Utils::Platform::GetCommandLineArgument(CMDLINE_WEBSOCKET_PORT);
if (portArgument != "") { if (portArgument != "") {
bool ok; bool ok;
uint16_t serverPort = portArgument.toUShort(&ok); uint16_t serverPort = portArgument.toUShort(&ok);
if (ok) { if (ok) {
blog(LOG_INFO, "[Config::Load] --websocket_port passed. Overriding WebSocket port with: %d", serverPort); blog(LOG_INFO,
"[Config::Load] --websocket_port passed. Overriding WebSocket port with: %d",
serverPort);
PortOverridden = true; PortOverridden = true;
ServerPort = serverPort; ServerPort = serverPort;
} else { } else {
blog(LOG_WARNING, "[Config::Load] Not overriding WebSocket port since integer conversion failed."); blog(LOG_WARNING,
"[Config::Load] Not overriding WebSocket port since integer conversion failed.");
} }
} }
// Process `--websocket_ipv4_only` override // Process `--websocket_ipv4_only` override
if (Utils::Platform::GetCommandLineFlagSet(CMDLINE_WEBSOCKET_IPV4_ONLY)) { if (Utils::Platform::GetCommandLineFlagSet(
blog(LOG_INFO, "[Config::Load] --websocket_ipv4_only passed. Binding only to IPv4 interfaces."); CMDLINE_WEBSOCKET_IPV4_ONLY)) {
blog(LOG_INFO,
"[Config::Load] --websocket_ipv4_only passed. Binding only to IPv4 interfaces.");
Ipv4Only = true; Ipv4Only = true;
} }
// Process `--websocket_password` override // Process `--websocket_password` override
QString passwordArgument = Utils::Platform::GetCommandLineArgument(CMDLINE_WEBSOCKET_PASSWORD); QString passwordArgument = Utils::Platform::GetCommandLineArgument(
CMDLINE_WEBSOCKET_PASSWORD);
if (passwordArgument != "") { if (passwordArgument != "") {
blog(LOG_INFO, "[Config::Load] --websocket_password passed. Overriding WebSocket password."); blog(LOG_INFO,
"[Config::Load] --websocket_password passed. Overriding WebSocket password.");
PasswordOverridden = true; PasswordOverridden = true;
AuthRequired = true; AuthRequired = true;
ServerPassword = passwordArgument; ServerPassword = passwordArgument;
@ -116,29 +134,37 @@ void Config::Load()
// Process `--websocket_debug` override // Process `--websocket_debug` override
if (Utils::Platform::GetCommandLineFlagSet(CMDLINE_WEBSOCKET_DEBUG)) { if (Utils::Platform::GetCommandLineFlagSet(CMDLINE_WEBSOCKET_DEBUG)) {
// Debug does not persist on reload, so we let people override it with a flag. // Debug does not persist on reload, so we let people override it with a flag.
blog(LOG_INFO, "[Config::Load] --websocket_debug passed. Enabling debug logging."); blog(LOG_INFO,
"[Config::Load] --websocket_debug passed. Enabling debug logging.");
DebugEnabled = true; DebugEnabled = true;
} }
} }
void Config::Save() void Config::Save()
{ {
config_t* obsConfig = GetConfigStore(); config_t *obsConfig = GetConfigStore();
if (!obsConfig) { if (!obsConfig) {
blog(LOG_ERROR, "[Config::Save] Unable to fetch OBS config!"); blog(LOG_ERROR, "[Config::Save] Unable to fetch OBS config!");
return; return;
} }
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD, FirstLoad); config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD,
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED, ServerEnabled); FirstLoad);
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED,
ServerEnabled);
if (!PortOverridden) { if (!PortOverridden) {
config_set_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT, ServerPort); config_set_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT,
ServerPort);
} }
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_BINDLOOPBACK, BindLoopback); config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_BINDLOOPBACK,
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS, AlertsEnabled); BindLoopback);
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS,
AlertsEnabled);
if (!PasswordOverridden) { if (!PasswordOverridden) {
config_set_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired); config_set_bool(obsConfig, CONFIG_SECTION_NAME,
config_set_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD, QT_TO_UTF8(ServerPassword)); PARAM_AUTHREQUIRED, AuthRequired);
config_set_string(obsConfig, CONFIG_SECTION_NAME,
PARAM_PASSWORD, QT_TO_UTF8(ServerPassword));
} }
config_save(obsConfig); config_save(obsConfig);
@ -146,22 +172,30 @@ void Config::Save()
void Config::SetDefaultsToGlobalStore() void Config::SetDefaultsToGlobalStore()
{ {
config_t* obsConfig = GetConfigStore(); config_t *obsConfig = GetConfigStore();
if (!obsConfig) { if (!obsConfig) {
blog(LOG_ERROR, "[Config::SetDefaultsToGlobalStore] Unable to fetch OBS config!"); blog(LOG_ERROR,
"[Config::SetDefaultsToGlobalStore] Unable to fetch OBS config!");
return; return;
} }
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD, FirstLoad); config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_FIRSTLOAD,
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED, ServerEnabled); FirstLoad);
config_set_default_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT, ServerPort); config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ENABLED,
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_BINDLOOPBACK, BindLoopback); ServerEnabled);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS, AlertsEnabled); config_set_default_uint(obsConfig, CONFIG_SECTION_NAME, PARAM_PORT,
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired); ServerPort);
config_set_default_string(obsConfig, CONFIG_SECTION_NAME, PARAM_PASSWORD, QT_TO_UTF8(ServerPassword)); config_set_default_bool(obsConfig, CONFIG_SECTION_NAME,
PARAM_BINDLOOPBACK, BindLoopback);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME, PARAM_ALERTS,
AlertsEnabled);
config_set_default_bool(obsConfig, CONFIG_SECTION_NAME,
PARAM_AUTHREQUIRED, AuthRequired);
config_set_default_string(obsConfig, CONFIG_SECTION_NAME,
PARAM_PASSWORD, QT_TO_UTF8(ServerPassword));
} }
config_t* Config::GetConfigStore() config_t *Config::GetConfigStore()
{ {
return obs_frontend_get_global_config(); return obs_frontend_get_global_config();
} }

View File

@ -30,7 +30,7 @@ struct Config {
void Load(); void Load();
void Save(); void Save();
void SetDefaultsToGlobalStore(); void SetDefaultsToGlobalStore();
config_t* GetConfigStore(); config_t *GetConfigStore();
std::atomic<bool> PortOverridden; std::atomic<bool> PortOverridden;
std::atomic<bool> PasswordOverridden; std::atomic<bool> PasswordOverridden;

View File

@ -3,7 +3,11 @@
#include "obs-websocket.h" #include "obs-websocket.h"
#include "utils/Json.h" #include "utils/Json.h"
#define RETURN_STATUS(status) { calldata_set_bool(cd, "success", status); return; } #define RETURN_STATUS(status) \
{ \
calldata_set_bool(cd, "success", status); \
return; \
}
#define RETURN_SUCCESS() RETURN_STATUS(true); #define RETURN_SUCCESS() RETURN_STATUS(true);
#define RETURN_FAILURE() RETURN_STATUS(false); #define RETURN_FAILURE() RETURN_STATUS(false);
@ -11,11 +15,12 @@ WebSocketApi::Vendor *get_vendor(calldata_t *cd)
{ {
void *voidVendor; void *voidVendor;
if (!calldata_get_ptr(cd, "vendor", &voidVendor)) { if (!calldata_get_ptr(cd, "vendor", &voidVendor)) {
blog(LOG_WARNING, "[WebSocketApi: get_vendor] Failed due to missing `vendor` pointer."); blog(LOG_WARNING,
"[WebSocketApi: get_vendor] Failed due to missing `vendor` pointer.");
return nullptr; return nullptr;
} }
return static_cast<WebSocketApi::Vendor*>(voidVendor); return static_cast<WebSocketApi::Vendor *>(voidVendor);
} }
WebSocketApi::WebSocketApi() WebSocketApi::WebSocketApi()
@ -24,17 +29,33 @@ WebSocketApi::WebSocketApi()
_procHandler = proc_handler_create(); _procHandler = proc_handler_create();
proc_handler_add(_procHandler, "bool get_api_version(out int version)", &get_api_version, nullptr); proc_handler_add(_procHandler, "bool get_api_version(out int version)",
proc_handler_add(_procHandler, "bool call_request(in string request_type, in string request_data, out ptr response)", &call_request, nullptr); &get_api_version, nullptr);
proc_handler_add(_procHandler, "bool vendor_register(in string name, out ptr vendor)", &vendor_register_cb, this); proc_handler_add(
proc_handler_add(_procHandler, "bool vendor_request_register(in ptr vendor, in string type, in ptr callback)", &vendor_request_register_cb, this); _procHandler,
proc_handler_add(_procHandler, "bool vendor_request_unregister(in ptr vendor, in string type)", &vendor_request_unregister_cb, this); "bool call_request(in string request_type, in string request_data, out ptr response)",
proc_handler_add(_procHandler, "bool vendor_event_emit(in ptr vendor, in string type, in ptr data)", &vendor_event_emit_cb, this); &call_request, nullptr);
proc_handler_add(_procHandler,
"bool vendor_register(in string name, out ptr vendor)",
&vendor_register_cb, this);
proc_handler_add(
_procHandler,
"bool vendor_request_register(in ptr vendor, in string type, in ptr callback)",
&vendor_request_register_cb, this);
proc_handler_add(
_procHandler,
"bool vendor_request_unregister(in ptr vendor, in string type)",
&vendor_request_unregister_cb, this);
proc_handler_add(
_procHandler,
"bool vendor_event_emit(in ptr vendor, in string type, in ptr data)",
&vendor_event_emit_cb, this);
proc_handler_t *ph = obs_get_proc_handler(); proc_handler_t *ph = obs_get_proc_handler();
assert(ph != NULL); assert(ph != NULL);
proc_handler_add(ph, "bool obs_websocket_api_get_ph(out ptr ph)", &get_ph_cb, this); proc_handler_add(ph, "bool obs_websocket_api_get_ph(out ptr ph)",
&get_ph_cb, this);
blog_debug("[WebSocketApi::WebSocketApi] Finished."); blog_debug("[WebSocketApi::WebSocketApi] Finished.");
} }
@ -46,7 +67,8 @@ WebSocketApi::~WebSocketApi()
proc_handler_destroy(_procHandler); proc_handler_destroy(_procHandler);
for (auto vendor : _vendors) { for (auto vendor : _vendors) {
blog_debug("[WebSocketApi::~WebSocketApi] Deleting vendor: %s", vendor.first.c_str()); blog_debug("[WebSocketApi::~WebSocketApi] Deleting vendor: %s",
vendor.first.c_str());
delete vendor.second; delete vendor.second;
} }
@ -58,7 +80,9 @@ void WebSocketApi::SetEventCallback(EventCallback cb)
_eventCallback = cb; _eventCallback = cb;
} }
enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::string vendorName, std::string requestType, obs_data_t *requestData, obs_data_t *responseData) enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(
std::string vendorName, std::string requestType,
obs_data_t *requestData, obs_data_t *responseData)
{ {
std::shared_lock l(_mutex); std::shared_lock l(_mutex);
@ -85,9 +109,9 @@ enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::str
void WebSocketApi::get_ph_cb(void *priv_data, calldata_t *cd) void WebSocketApi::get_ph_cb(void *priv_data, calldata_t *cd)
{ {
auto c = static_cast<WebSocketApi*>(priv_data); auto c = static_cast<WebSocketApi *>(priv_data);
calldata_set_ptr(cd, "ph", (void*)c->_procHandler); calldata_set_ptr(cd, "ph", (void *)c->_procHandler);
RETURN_SUCCESS(); RETURN_SUCCESS();
} }
@ -107,7 +131,8 @@ void WebSocketApi::call_request(void *, calldata_t *cd)
if (!request_type) if (!request_type)
RETURN_FAILURE(); RETURN_FAILURE();
auto response = static_cast<obs_websocket_request_response*>(bzalloc(sizeof(struct obs_websocket_request_response))); auto response = static_cast<obs_websocket_request_response *>(
bzalloc(sizeof(struct obs_websocket_request_response)));
if (!response) if (!response)
RETURN_FAILURE(); RETURN_FAILURE();
@ -129,18 +154,22 @@ void WebSocketApi::call_request(void *, calldata_t *cd)
calldata_set_ptr(cd, "response", response); calldata_set_ptr(cd, "response", response);
blog_debug("[WebSocketApi::call_request] Request %s called, response status code is %u", request_type, response->status_code); blog_debug(
"[WebSocketApi::call_request] Request %s called, response status code is %u",
request_type, response->status_code);
RETURN_SUCCESS(); RETURN_SUCCESS();
} }
void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd) void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
{ {
auto c = static_cast<WebSocketApi*>(priv_data); auto c = static_cast<WebSocketApi *>(priv_data);
const char *vendorName; const char *vendorName;
if (!calldata_get_string(cd, "name", &vendorName) || strlen(vendorName) == 0) { if (!calldata_get_string(cd, "name", &vendorName) ||
blog(LOG_WARNING, "[WebSocketApi::vendor_register_cb] Failed due to missing `name` string."); strlen(vendorName) == 0) {
blog(LOG_WARNING,
"[WebSocketApi::vendor_register_cb] Failed due to missing `name` string.");
RETURN_FAILURE(); RETURN_FAILURE();
} }
@ -148,18 +177,22 @@ void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
std::unique_lock l(c->_mutex); std::unique_lock l(c->_mutex);
if (c->_vendors.count(vendorName)) { if (c->_vendors.count(vendorName)) {
blog(LOG_WARNING, "[WebSocketApi::vendor_register_cb] Failed because `%s` is already a registered vendor.", vendorName); blog(LOG_WARNING,
"[WebSocketApi::vendor_register_cb] Failed because `%s` is already a registered vendor.",
vendorName);
RETURN_FAILURE(); RETURN_FAILURE();
} }
Vendor* v = new Vendor(); Vendor *v = new Vendor();
v->_name = vendorName; v->_name = vendorName;
c->_vendors[vendorName] = v; c->_vendors[vendorName] = v;
blog_debug("[WebSocketApi::vendor_register_cb] [vendorName: %s] Registered new vendor.", v->_name.c_str()); blog_debug(
"[WebSocketApi::vendor_register_cb] [vendorName: %s] Registered new vendor.",
v->_name.c_str());
calldata_set_ptr(cd, "vendor", static_cast<void*>(v)); calldata_set_ptr(cd, "vendor", static_cast<void *>(v));
RETURN_SUCCESS(); RETURN_SUCCESS();
} }
@ -171,29 +204,38 @@ void WebSocketApi::vendor_request_register_cb(void *, calldata_t *cd)
RETURN_FAILURE(); RETURN_FAILURE();
const char *requestType; const char *requestType;
if (!calldata_get_string(cd, "type", &requestType) || strlen(requestType) == 0) { if (!calldata_get_string(cd, "type", &requestType) ||
blog(LOG_WARNING, "[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed due to missing or empty `type` string.", v->_name.c_str()); strlen(requestType) == 0) {
blog(LOG_WARNING,
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed due to missing or empty `type` string.",
v->_name.c_str());
RETURN_FAILURE(); RETURN_FAILURE();
} }
void *voidCallback; void *voidCallback;
if (!calldata_get_ptr(cd, "callback", &voidCallback) || !voidCallback) { if (!calldata_get_ptr(cd, "callback", &voidCallback) || !voidCallback) {
blog(LOG_WARNING, "[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed due to missing `callback` pointer.", v->_name.c_str()); blog(LOG_WARNING,
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed due to missing `callback` pointer.",
v->_name.c_str());
RETURN_FAILURE(); RETURN_FAILURE();
} }
auto cb = static_cast<obs_websocket_request_callback*>(voidCallback); auto cb = static_cast<obs_websocket_request_callback *>(voidCallback);
std::unique_lock l(v->_mutex); std::unique_lock l(v->_mutex);
if (v->_requests.count(requestType)) { if (v->_requests.count(requestType)) {
blog(LOG_WARNING, "[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed because `%s` is already a registered request.", v->_name.c_str(), requestType); blog(LOG_WARNING,
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed because `%s` is already a registered request.",
v->_name.c_str(), requestType);
RETURN_FAILURE(); RETURN_FAILURE();
} }
v->_requests[requestType] = *cb; v->_requests[requestType] = *cb;
blog_debug("[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Registered new vendor request: %s", v->_name.c_str(), requestType); blog_debug(
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Registered new vendor request: %s",
v->_name.c_str(), requestType);
RETURN_SUCCESS(); RETURN_SUCCESS();
} }
@ -205,46 +247,58 @@ void WebSocketApi::vendor_request_unregister_cb(void *, calldata_t *cd)
RETURN_FAILURE(); RETURN_FAILURE();
const char *requestType; const char *requestType;
if (!calldata_get_string(cd, "type", &requestType) || strlen(requestType) == 0) { if (!calldata_get_string(cd, "type", &requestType) ||
blog(LOG_WARNING, "[WebSocketApi::vendor_request_unregister_cb] [vendorName: %s] Failed due to missing `type` string.", v->_name.c_str()); strlen(requestType) == 0) {
blog(LOG_WARNING,
"[WebSocketApi::vendor_request_unregister_cb] [vendorName: %s] Failed due to missing `type` string.",
v->_name.c_str());
RETURN_FAILURE(); RETURN_FAILURE();
} }
std::unique_lock l(v->_mutex); std::unique_lock l(v->_mutex);
if (!v->_requests.count(requestType)) { if (!v->_requests.count(requestType)) {
blog(LOG_WARNING, "[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed because `%s` is not a registered request.", v->_name.c_str(), requestType); blog(LOG_WARNING,
"[WebSocketApi::vendor_request_register_cb] [vendorName: %s] Failed because `%s` is not a registered request.",
v->_name.c_str(), requestType);
RETURN_FAILURE(); RETURN_FAILURE();
} }
v->_requests.erase(requestType); v->_requests.erase(requestType);
blog_debug("[WebSocketApi::vendor_request_unregister_cb] [vendorName: %s] Unregistered vendor request: %s", v->_name.c_str(), requestType); blog_debug(
"[WebSocketApi::vendor_request_unregister_cb] [vendorName: %s] Unregistered vendor request: %s",
v->_name.c_str(), requestType);
RETURN_SUCCESS(); RETURN_SUCCESS();
} }
void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd) void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd)
{ {
auto c = static_cast<WebSocketApi*>(priv_data); auto c = static_cast<WebSocketApi *>(priv_data);
Vendor *v = get_vendor(cd); Vendor *v = get_vendor(cd);
if (!v) if (!v)
RETURN_FAILURE(); RETURN_FAILURE();
const char *eventType; const char *eventType;
if (!calldata_get_string(cd, "type", &eventType) || strlen(eventType) == 0) { if (!calldata_get_string(cd, "type", &eventType) ||
blog(LOG_WARNING, "[WebSocketApi::vendor_event_emit_cb] [vendorName: %s] Failed due to missing `type` string.", v->_name.c_str()); strlen(eventType) == 0) {
blog(LOG_WARNING,
"[WebSocketApi::vendor_event_emit_cb] [vendorName: %s] Failed due to missing `type` string.",
v->_name.c_str());
RETURN_FAILURE(); RETURN_FAILURE();
} }
void *voidEventData; void *voidEventData;
if (!calldata_get_ptr(cd, "data", &voidEventData)) { if (!calldata_get_ptr(cd, "data", &voidEventData)) {
blog(LOG_WARNING, "[WebSocketApi::vendor_event_emit_cb] [vendorName: %s] Failed due to missing `data` pointer.", v->_name.c_str()); blog(LOG_WARNING,
"[WebSocketApi::vendor_event_emit_cb] [vendorName: %s] Failed due to missing `data` pointer.",
v->_name.c_str());
RETURN_FAILURE(); RETURN_FAILURE();
} }
auto eventData = static_cast<obs_data_t*>(voidEventData); auto eventData = static_cast<obs_data_t *>(voidEventData);
if (!c->_eventCallback) if (!c->_eventCallback)
RETURN_FAILURE(); RETURN_FAILURE();

View File

@ -10,39 +10,44 @@
#include "../lib/obs-websocket-api.h" #include "../lib/obs-websocket-api.h"
class WebSocketApi { class WebSocketApi {
public: public:
enum RequestReturnCode { enum RequestReturnCode {
Normal, Normal,
NoVendor, NoVendor,
NoVendorRequest, NoVendorRequest,
}; };
typedef std::function<void(std::string, std::string, obs_data_t*)> EventCallback; typedef std::function<void(std::string, std::string, obs_data_t *)>
EventCallback;
struct Vendor { struct Vendor {
std::shared_mutex _mutex;
std::string _name;
std::map<std::string, obs_websocket_request_callback> _requests;
};
WebSocketApi();
~WebSocketApi();
void SetEventCallback(EventCallback cb);
enum RequestReturnCode PerformVendorRequest(std::string vendorName, std::string requestName, obs_data_t *requestData, obs_data_t *responseData);
static void get_ph_cb(void *priv_data, calldata_t *cd);
static void get_api_version(void *, calldata_t *cd);
static void call_request(void *, calldata_t *cd);
static void vendor_register_cb(void *priv_data, calldata_t *cd);
static void vendor_request_register_cb(void *priv_data, calldata_t *cd);
static void vendor_request_unregister_cb(void *priv_data, calldata_t *cd);
static void vendor_event_emit_cb(void *priv_data, calldata_t *cd);
private:
std::shared_mutex _mutex; std::shared_mutex _mutex;
EventCallback _eventCallback; std::string _name;
proc_handler_t *_procHandler; std::map<std::string, obs_websocket_request_callback> _requests;
std::map<std::string, Vendor*> _vendors; };
WebSocketApi();
~WebSocketApi();
void SetEventCallback(EventCallback cb);
enum RequestReturnCode PerformVendorRequest(std::string vendorName,
std::string requestName,
obs_data_t *requestData,
obs_data_t *responseData);
static void get_ph_cb(void *priv_data, calldata_t *cd);
static void get_api_version(void *, calldata_t *cd);
static void call_request(void *, calldata_t *cd);
static void vendor_register_cb(void *priv_data, calldata_t *cd);
static void vendor_request_register_cb(void *priv_data, calldata_t *cd);
static void vendor_request_unregister_cb(void *priv_data,
calldata_t *cd);
static void vendor_event_emit_cb(void *priv_data, calldata_t *cd);
private:
std::shared_mutex _mutex;
EventCallback _eventCallback;
proc_handler_t *_procHandler;
std::map<std::string, Vendor *> _vendors;
}; };

File diff suppressed because it is too large Load Diff

View File

@ -29,123 +29,181 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../utils/Obs_VolumeMeter.h" #include "../utils/Obs_VolumeMeter.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
class EventHandler class EventHandler {
{ public:
public: EventHandler();
EventHandler(); ~EventHandler();
~EventHandler();
typedef std::function<void(uint64_t, std::string, json, uint8_t)> BroadcastCallback; typedef std::function<void(uint64_t, std::string, json, uint8_t)>
void SetBroadcastCallback(BroadcastCallback cb); BroadcastCallback;
typedef std::function<void()> ObsLoadedCallback; void SetBroadcastCallback(BroadcastCallback cb);
void SetObsLoadedCallback(ObsLoadedCallback cb); typedef std::function<void()> ObsLoadedCallback;
void SetObsLoadedCallback(ObsLoadedCallback cb);
void ProcessSubscription(uint64_t eventSubscriptions); void ProcessSubscription(uint64_t eventSubscriptions);
void ProcessUnsubscription(uint64_t eventSubscriptions); void ProcessUnsubscription(uint64_t eventSubscriptions);
private: private:
BroadcastCallback _broadcastCallback; BroadcastCallback _broadcastCallback;
ObsLoadedCallback _obsLoadedCallback; ObsLoadedCallback _obsLoadedCallback;
std::atomic<bool> _obsLoaded; std::atomic<bool> _obsLoaded;
std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler; std::unique_ptr<Utils::Obs::VolumeMeter::Handler>
std::atomic<uint64_t> _inputVolumeMetersRef; _inputVolumeMetersHandler;
std::atomic<uint64_t> _inputActiveStateChangedRef; std::atomic<uint64_t> _inputVolumeMetersRef;
std::atomic<uint64_t> _inputShowStateChangedRef; std::atomic<uint64_t> _inputActiveStateChangedRef;
std::atomic<uint64_t> _sceneItemTransformChangedRef; std::atomic<uint64_t> _inputShowStateChangedRef;
std::atomic<uint64_t> _sceneItemTransformChangedRef;
void ConnectSourceSignals(obs_source_t *source); void ConnectSourceSignals(obs_source_t *source);
void DisconnectSourceSignals(obs_source_t *source); void DisconnectSourceSignals(obs_source_t *source);
void BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData = nullptr, uint8_t rpcVersion = 0); void BroadcastEvent(uint64_t requiredIntent, std::string eventType,
json eventData = nullptr, uint8_t rpcVersion = 0);
// Signal handler: frontend // Signal handler: frontend
static void OnFrontendEvent(enum obs_frontend_event event, void *private_data); static void OnFrontendEvent(enum obs_frontend_event event,
void *private_data);
// Signal handler: libobs // Signal handler: libobs
static void SourceCreatedMultiHandler(void *param, calldata_t *data); static void SourceCreatedMultiHandler(void *param, calldata_t *data);
static void SourceDestroyedMultiHandler(void *param, calldata_t *data); static void SourceDestroyedMultiHandler(void *param, calldata_t *data);
static void SourceRemovedMultiHandler(void *param, calldata_t *data); static void SourceRemovedMultiHandler(void *param, calldata_t *data);
// Signal handler: source // Signal handler: source
static void SourceRenamedMultiHandler(void *param, calldata_t *data); static void SourceRenamedMultiHandler(void *param, calldata_t *data);
static void SourceMediaPauseMultiHandler(void *param, calldata_t *data); static void SourceMediaPauseMultiHandler(void *param, calldata_t *data);
static void SourceMediaPlayMultiHandler(void *param, calldata_t *data); static void SourceMediaPlayMultiHandler(void *param, calldata_t *data);
static void SourceMediaRestartMultiHandler(void *param, calldata_t *data); static void SourceMediaRestartMultiHandler(void *param,
static void SourceMediaStopMultiHandler(void *param, calldata_t *data); calldata_t *data);
static void SourceMediaNextMultiHandler(void *param, calldata_t *data); static void SourceMediaStopMultiHandler(void *param, calldata_t *data);
static void SourceMediaPreviousMultiHandler(void *param, calldata_t *data); static void SourceMediaNextMultiHandler(void *param, calldata_t *data);
static void SourceMediaPreviousMultiHandler(void *param,
calldata_t *data);
// General
void HandleExitStarted();
void HandleStudioModeStateChanged(bool enabled);
// General // Config
void HandleExitStarted(); void HandleCurrentSceneCollectionChanging();
void HandleStudioModeStateChanged(bool enabled); void HandleCurrentSceneCollectionChanged();
void HandleSceneCollectionListChanged();
void HandleCurrentProfileChanging();
void HandleCurrentProfileChanged();
void HandleProfileListChanged();
// Config // Scenes
void HandleCurrentSceneCollectionChanging(); void HandleSceneCreated(obs_source_t *source);
void HandleCurrentSceneCollectionChanged(); void HandleSceneRemoved(obs_source_t *source);
void HandleSceneCollectionListChanged(); void HandleSceneNameChanged(obs_source_t *source,
void HandleCurrentProfileChanging(); std::string oldSceneName,
void HandleCurrentProfileChanged(); std::string sceneName);
void HandleProfileListChanged(); void HandleCurrentProgramSceneChanged();
void HandleCurrentPreviewSceneChanged();
void HandleSceneListChanged();
// Scenes // Inputs
void HandleSceneCreated(obs_source_t *source); void HandleInputCreated(obs_source_t *source);
void HandleSceneRemoved(obs_source_t *source); void HandleInputRemoved(obs_source_t *source);
void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName); void HandleInputNameChanged(obs_source_t *source,
void HandleCurrentProgramSceneChanged(); std::string oldInputName,
void HandleCurrentPreviewSceneChanged(); std::string inputName);
void HandleSceneListChanged(); void HandleInputVolumeMeters(
std::vector<json> inputs); // AudioMeter::Handler callback
static void
HandleInputActiveStateChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleInputShowStateChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleInputMuteStateChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleInputVolumeChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleInputAudioBalanceChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleInputAudioSyncOffsetChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleInputAudioTracksChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleInputAudioMonitorTypeChanged(void *param,
calldata_t *data); // Direct callback
// Inputs // Transitions
void HandleInputCreated(obs_source_t *source); void HandleCurrentSceneTransitionChanged();
void HandleInputRemoved(obs_source_t *source); void HandleCurrentSceneTransitionDurationChanged();
void HandleInputNameChanged(obs_source_t *source, std::string oldInputName, std::string inputName); static void
void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback HandleSceneTransitionStarted(void *param,
static void HandleInputActiveStateChanged(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback static void
static void HandleInputMuteStateChanged(void *param, calldata_t *data); // Direct callback HandleSceneTransitionEnded(void *param,
static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleInputAudioBalanceChanged(void *param, calldata_t *data); // Direct callback static void
static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback HandleSceneTransitionVideoEnded(void *param,
static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback
// Transitions // Filters
void HandleCurrentSceneTransitionChanged(); static void FilterAddMultiHandler(void *param,
void HandleCurrentSceneTransitionDurationChanged(); calldata_t *data); // Direct callback
static void HandleSceneTransitionStarted(void *param, calldata_t *data); // Direct callback static void
static void HandleSceneTransitionEnded(void *param, calldata_t *data); // Direct callback FilterRemoveMultiHandler(void *param,
static void HandleSceneTransitionVideoEnded(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void
HandleSourceFilterListReindexed(void *param,
calldata_t *data); // Direct callback
void HandleSourceFilterCreated(obs_source_t *source,
obs_source_t *filter);
void HandleSourceFilterRemoved(obs_source_t *source,
obs_source_t *filter);
static void
HandleSourceFilterNameChanged(void *param,
calldata_t *data); // Direct callback
static void HandleSourceFilterEnableStateChanged(
void *param, calldata_t *data); // Direct callback
// Filters // Outputs
static void FilterAddMultiHandler(void *param, calldata_t *data); // Direct callback void HandleStreamStateChanged(ObsOutputState state);
static void FilterRemoveMultiHandler(void *param, calldata_t *data); // Direct callback void HandleRecordStateChanged(ObsOutputState state);
static void HandleSourceFilterListReindexed(void *param, calldata_t *data); // Direct callback void HandleReplayBufferStateChanged(ObsOutputState state);
void HandleSourceFilterCreated(obs_source_t *source, obs_source_t *filter); void HandleVirtualcamStateChanged(ObsOutputState state);
void HandleSourceFilterRemoved(obs_source_t *source, obs_source_t *filter); void HandleReplayBufferSaved();
static void HandleSourceFilterNameChanged(void *param, calldata_t *data); // Direct callback
static void HandleSourceFilterEnableStateChanged(void *param, calldata_t *data); // Direct callback
// Outputs // Scene Items
void HandleStreamStateChanged(ObsOutputState state); static void HandleSceneItemCreated(void *param,
void HandleRecordStateChanged(ObsOutputState state); calldata_t *data); // Direct callback
void HandleReplayBufferStateChanged(ObsOutputState state); static void HandleSceneItemRemoved(void *param,
void HandleVirtualcamStateChanged(ObsOutputState state); calldata_t *data); // Direct callback
void HandleReplayBufferSaved(); static void
HandleSceneItemListReindexed(void *param,
calldata_t *data); // Direct callback
static void
HandleSceneItemEnableStateChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleSceneItemLockStateChanged(void *param,
calldata_t *data); // Direct callback
static void
HandleSceneItemSelected(void *param,
calldata_t *data); // Direct callback
static void
HandleSceneItemTransformChanged(void *param,
calldata_t *data); // Direct callback
// Scene Items // Media Inputs
static void HandleSceneItemCreated(void *param, calldata_t *data); // Direct callback static void
static void HandleSceneItemRemoved(void *param, calldata_t *data); // Direct callback HandleMediaInputPlaybackStarted(void *param,
static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleSceneItemEnableStateChanged(void *param, calldata_t *data); // Direct callback static void
static void HandleSceneItemLockStateChanged(void *param, calldata_t *data); // Direct callback HandleMediaInputPlaybackEnded(void *param,
static void HandleSceneItemSelected(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleSceneItemTransformChanged(void *param, calldata_t *data); // Direct callback void HandleMediaInputActionTriggered(obs_source_t *source,
ObsMediaInputAction action);
// Media Inputs
static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback
static void HandleMediaInputPlaybackEnded(void *param, calldata_t *data); // Direct callback
void HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action);
}; };

View File

@ -38,8 +38,10 @@ with this program. If not, see <https://www.gnu.org/licenses/>
void EventHandler::HandleCurrentSceneCollectionChanging() void EventHandler::HandleCurrentSceneCollectionChanging()
{ {
json eventData; json eventData;
eventData["sceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection(); eventData["sceneCollectionName"] =
BroadcastEvent(EventSubscription::Config, "CurrentSceneCollectionChanging", eventData); Utils::Obs::StringHelper::GetCurrentSceneCollection();
BroadcastEvent(EventSubscription::Config,
"CurrentSceneCollectionChanging", eventData);
} }
/** /**
@ -60,8 +62,10 @@ void EventHandler::HandleCurrentSceneCollectionChanging()
void EventHandler::HandleCurrentSceneCollectionChanged() void EventHandler::HandleCurrentSceneCollectionChanged()
{ {
json eventData; json eventData;
eventData["sceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection(); eventData["sceneCollectionName"] =
BroadcastEvent(EventSubscription::Config, "CurrentSceneCollectionChanged", eventData); Utils::Obs::StringHelper::GetCurrentSceneCollection();
BroadcastEvent(EventSubscription::Config,
"CurrentSceneCollectionChanged", eventData);
} }
/** /**
@ -80,8 +84,10 @@ void EventHandler::HandleCurrentSceneCollectionChanged()
void EventHandler::HandleSceneCollectionListChanged() void EventHandler::HandleSceneCollectionListChanged()
{ {
json eventData; json eventData;
eventData["sceneCollections"] = Utils::Obs::ArrayHelper::GetSceneCollectionList(); eventData["sceneCollections"] =
BroadcastEvent(EventSubscription::Config, "SceneCollectionListChanged", eventData); Utils::Obs::ArrayHelper::GetSceneCollectionList();
BroadcastEvent(EventSubscription::Config, "SceneCollectionListChanged",
eventData);
} }
/** /**
@ -100,8 +106,10 @@ void EventHandler::HandleSceneCollectionListChanged()
void EventHandler::HandleCurrentProfileChanging() void EventHandler::HandleCurrentProfileChanging()
{ {
json eventData; json eventData;
eventData["profileName"] = Utils::Obs::StringHelper::GetCurrentProfile(); eventData["profileName"] =
BroadcastEvent(EventSubscription::Config, "CurrentProfileChanging", eventData); Utils::Obs::StringHelper::GetCurrentProfile();
BroadcastEvent(EventSubscription::Config, "CurrentProfileChanging",
eventData);
} }
/** /**
@ -120,8 +128,10 @@ void EventHandler::HandleCurrentProfileChanging()
void EventHandler::HandleCurrentProfileChanged() void EventHandler::HandleCurrentProfileChanged()
{ {
json eventData; json eventData;
eventData["profileName"] = Utils::Obs::StringHelper::GetCurrentProfile(); eventData["profileName"] =
BroadcastEvent(EventSubscription::Config, "CurrentProfileChanged", eventData); Utils::Obs::StringHelper::GetCurrentProfile();
BroadcastEvent(EventSubscription::Config, "CurrentProfileChanged",
eventData);
} }
/** /**
@ -141,5 +151,6 @@ void EventHandler::HandleProfileListChanged()
{ {
json eventData; json eventData;
eventData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList(); eventData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList();
BroadcastEvent(EventSubscription::Config, "ProfileListChanged", eventData); BroadcastEvent(EventSubscription::Config, "ProfileListChanged",
eventData);
} }

View File

@ -21,7 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
void EventHandler::FilterAddMultiHandler(void *param, calldata_t *data) void EventHandler::FilterAddMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter"); obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter");
@ -36,7 +36,7 @@ void EventHandler::FilterAddMultiHandler(void *param, calldata_t *data)
void EventHandler::FilterRemoveMultiHandler(void *param, calldata_t *data) void EventHandler::FilterRemoveMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter"); obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter");
@ -63,9 +63,10 @@ void EventHandler::FilterRemoveMultiHandler(void *param, calldata_t *data)
* @api events * @api events
* @category filters * @category filters
*/ */
void EventHandler::HandleSourceFilterListReindexed(void *param, calldata_t *data) void EventHandler::HandleSourceFilterListReindexed(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -73,8 +74,10 @@ void EventHandler::HandleSourceFilterListReindexed(void *param, calldata_t *data
json eventData; json eventData;
eventData["sourceName"] = obs_source_get_name(source); eventData["sourceName"] = obs_source_get_name(source);
eventData["filters"] = Utils::Obs::ArrayHelper::GetSourceFilterList(source); eventData["filters"] =
eventHandler->BroadcastEvent(EventSubscription::Filters, "SourceFilterListReindexed", eventData); Utils::Obs::ArrayHelper::GetSourceFilterList(source);
eventHandler->BroadcastEvent(EventSubscription::Filters,
"SourceFilterListReindexed", eventData);
} }
/** /**
@ -95,20 +98,26 @@ void EventHandler::HandleSourceFilterListReindexed(void *param, calldata_t *data
* @api events * @api events
* @category filters * @category filters
*/ */
void EventHandler::HandleSourceFilterCreated(obs_source_t *source, obs_source_t *filter) void EventHandler::HandleSourceFilterCreated(obs_source_t *source,
obs_source_t *filter)
{ {
std::string filterKind = obs_source_get_id(filter); std::string filterKind = obs_source_get_id(filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(filter); OBSDataAutoRelease filterSettings = obs_source_get_settings(filter);
OBSDataAutoRelease defaultFilterSettings = obs_get_source_defaults(filterKind.c_str()); OBSDataAutoRelease defaultFilterSettings =
obs_get_source_defaults(filterKind.c_str());
json eventData; json eventData;
eventData["sourceName"] = obs_source_get_name(source); eventData["sourceName"] = obs_source_get_name(source);
eventData["filterName"] = obs_source_get_name(filter); eventData["filterName"] = obs_source_get_name(filter);
eventData["filterKind"] = filterKind; eventData["filterKind"] = filterKind;
eventData["filterIndex"] = Utils::Obs::NumberHelper::GetSourceFilterIndex(source, filter); eventData["filterIndex"] =
eventData["filterSettings"] = Utils::Json::ObsDataToJson(filterSettings); Utils::Obs::NumberHelper::GetSourceFilterIndex(source, filter);
eventData["defaultFilterSettings"] = Utils::Json::ObsDataToJson(defaultFilterSettings, true); eventData["filterSettings"] =
BroadcastEvent(EventSubscription::Filters, "SourceFilterCreated", eventData); Utils::Json::ObsDataToJson(filterSettings);
eventData["defaultFilterSettings"] =
Utils::Json::ObsDataToJson(defaultFilterSettings, true);
BroadcastEvent(EventSubscription::Filters, "SourceFilterCreated",
eventData);
} }
/** /**
@ -125,12 +134,14 @@ void EventHandler::HandleSourceFilterCreated(obs_source_t *source, obs_source_t
* @api events * @api events
* @category filters * @category filters
*/ */
void EventHandler::HandleSourceFilterRemoved(obs_source_t *source, obs_source_t *filter) void EventHandler::HandleSourceFilterRemoved(obs_source_t *source,
obs_source_t *filter)
{ {
json eventData; json eventData;
eventData["sourceName"] = obs_source_get_name(source); eventData["sourceName"] = obs_source_get_name(source);
eventData["filterName"] = obs_source_get_name(filter); eventData["filterName"] = obs_source_get_name(filter);
BroadcastEvent(EventSubscription::Filters, "SourceFilterRemoved", eventData); BroadcastEvent(EventSubscription::Filters, "SourceFilterRemoved",
eventData);
} }
/** /**
@ -150,17 +161,19 @@ void EventHandler::HandleSourceFilterRemoved(obs_source_t *source, obs_source_t
*/ */
void EventHandler::HandleSourceFilterNameChanged(void *param, calldata_t *data) void EventHandler::HandleSourceFilterNameChanged(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source");
if (!filter) if (!filter)
return; return;
json eventData; json eventData;
eventData["sourceName"] = obs_source_get_name(obs_filter_get_parent(filter)); eventData["sourceName"] =
obs_source_get_name(obs_filter_get_parent(filter));
eventData["oldFilterName"] = calldata_string(data, "prev_name"); eventData["oldFilterName"] = calldata_string(data, "prev_name");
eventData["filterName"] = calldata_string(data, "new_name"); eventData["filterName"] = calldata_string(data, "new_name");
eventHandler->BroadcastEvent(EventSubscription::Filters, "SourceFilterNameChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Filters,
"SourceFilterNameChanged", eventData);
} }
/** /**
@ -178,9 +191,10 @@ void EventHandler::HandleSourceFilterNameChanged(void *param, calldata_t *data)
* @api events * @api events
* @category filters * @category filters
*/ */
void EventHandler::HandleSourceFilterEnableStateChanged(void *param, calldata_t *data) void EventHandler::HandleSourceFilterEnableStateChanged(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source");
if (!filter) if (!filter)
@ -197,5 +211,7 @@ void EventHandler::HandleSourceFilterEnableStateChanged(void *param, calldata_t
eventData["sourceName"] = obs_source_get_name(source); eventData["sourceName"] = obs_source_get_name(source);
eventData["filterName"] = obs_source_get_name(filter); eventData["filterName"] = obs_source_get_name(filter);
eventData["filterEnabled"] = filterEnabled; eventData["filterEnabled"] = filterEnabled;
eventHandler->BroadcastEvent(EventSubscription::Filters, "SourceFilterEnableStateChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Filters,
"SourceFilterEnableStateChanged",
eventData);
} }

View File

@ -40,14 +40,17 @@ void EventHandler::HandleInputCreated(obs_source_t *source)
{ {
std::string inputKind = obs_source_get_id(source); std::string inputKind = obs_source_get_id(source);
OBSDataAutoRelease inputSettings = obs_source_get_settings(source); OBSDataAutoRelease inputSettings = obs_source_get_settings(source);
OBSDataAutoRelease defaultInputSettings = obs_get_source_defaults(inputKind.c_str()); OBSDataAutoRelease defaultInputSettings =
obs_get_source_defaults(inputKind.c_str());
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["inputKind"] = inputKind; eventData["inputKind"] = inputKind;
eventData["unversionedInputKind"] = obs_source_get_unversioned_id(source); eventData["unversionedInputKind"] =
obs_source_get_unversioned_id(source);
eventData["inputSettings"] = Utils::Json::ObsDataToJson(inputSettings); eventData["inputSettings"] = Utils::Json::ObsDataToJson(inputSettings);
eventData["defaultInputSettings"] = Utils::Json::ObsDataToJson(defaultInputSettings, true); eventData["defaultInputSettings"] =
Utils::Json::ObsDataToJson(defaultInputSettings, true);
BroadcastEvent(EventSubscription::Inputs, "InputCreated", eventData); BroadcastEvent(EventSubscription::Inputs, "InputCreated", eventData);
} }
@ -85,12 +88,15 @@ void EventHandler::HandleInputRemoved(obs_source_t *source)
* @api events * @api events
* @category inputs * @category inputs
*/ */
void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputName, std::string inputName) void EventHandler::HandleInputNameChanged(obs_source_t *,
std::string oldInputName,
std::string inputName)
{ {
json eventData; json eventData;
eventData["oldInputName"] = oldInputName; eventData["oldInputName"] = oldInputName;
eventData["inputName"] = inputName; eventData["inputName"] = inputName;
BroadcastEvent(EventSubscription::Inputs, "InputNameChanged", eventData); BroadcastEvent(EventSubscription::Inputs, "InputNameChanged",
eventData);
} }
/** /**
@ -111,7 +117,7 @@ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputNa
*/ */
void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data) void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
if (!eventHandler->_inputActiveStateChangedRef.load()) if (!eventHandler->_inputActiveStateChangedRef.load())
return; return;
@ -126,7 +132,8 @@ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["videoActive"] = obs_source_active(source); eventData["videoActive"] = obs_source_active(source);
eventHandler->BroadcastEvent(EventSubscription::InputActiveStateChanged, "InputActiveStateChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::InputActiveStateChanged,
"InputActiveStateChanged", eventData);
} }
/** /**
@ -147,7 +154,7 @@ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data) void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
if (!eventHandler->_inputShowStateChangedRef.load()) if (!eventHandler->_inputShowStateChangedRef.load())
return; return;
@ -162,7 +169,8 @@ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["videoShowing"] = obs_source_showing(source); eventData["videoShowing"] = obs_source_showing(source);
eventHandler->BroadcastEvent(EventSubscription::InputShowStateChanged, "InputShowStateChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::InputShowStateChanged,
"InputShowStateChanged", eventData);
} }
/** /**
@ -181,7 +189,7 @@ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data) void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -193,7 +201,8 @@ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["inputMuted"] = obs_source_muted(source); eventData["inputMuted"] = obs_source_muted(source);
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputMuteStateChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Inputs,
"InputMuteStateChanged", eventData);
} }
/** /**
@ -213,7 +222,7 @@ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data) void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -233,7 +242,8 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["inputVolumeMul"] = inputVolumeMul; eventData["inputVolumeMul"] = inputVolumeMul;
eventData["inputVolumeDb"] = inputVolumeDb; eventData["inputVolumeDb"] = inputVolumeDb;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputVolumeChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Inputs,
"InputVolumeChanged", eventData);
} }
/** /**
@ -252,7 +262,7 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
*/ */
void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data) void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -266,7 +276,8 @@ void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data)
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["inputAudioBalance"] = inputAudioBalance; eventData["inputAudioBalance"] = inputAudioBalance;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioBalanceChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Inputs,
"InputAudioBalanceChanged", eventData);
} }
/** /**
@ -283,9 +294,10 @@ void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data)
* @api events * @api events
* @category inputs * @category inputs
*/ */
void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data) void EventHandler::HandleInputAudioSyncOffsetChanged(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -299,7 +311,8 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["inputAudioSyncOffset"] = inputAudioSyncOffset / 1000000; eventData["inputAudioSyncOffset"] = inputAudioSyncOffset / 1000000;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioSyncOffsetChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Inputs,
"InputAudioSyncOffsetChanged", eventData);
} }
/** /**
@ -318,7 +331,7 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da
*/ */
void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data) void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -331,13 +344,15 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
json inputAudioTracks; json inputAudioTracks;
for (long long i = 0; i < MAX_AUDIO_MIXES; i++) { for (long long i = 0; i < MAX_AUDIO_MIXES; i++) {
inputAudioTracks[std::to_string(i + 1)] = (bool)((tracks >> i) & 1); inputAudioTracks[std::to_string(i + 1)] =
(bool)((tracks >> i) & 1);
} }
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["inputAudioTracks"] = inputAudioTracks; eventData["inputAudioTracks"] = inputAudioTracks;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioTracksChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Inputs,
"InputAudioTracksChanged", eventData);
} }
/** /**
@ -360,9 +375,10 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
* @api events * @api events
* @category inputs * @category inputs
*/ */
void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data) void EventHandler::HandleInputAudioMonitorTypeChanged(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -371,14 +387,17 @@ void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *d
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return; return;
enum obs_monitoring_type monitorType = (obs_monitoring_type)calldata_int(data, "type"); enum obs_monitoring_type monitorType =
(obs_monitoring_type)calldata_int(data, "type");
std::string monitorTypeString = Utils::Obs::StringHelper::GetInputMonitorType(monitorType); std::string monitorTypeString =
Utils::Obs::StringHelper::GetInputMonitorType(monitorType);
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["monitorType"] = monitorTypeString; eventData["monitorType"] = monitorTypeString;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioMonitorTypeChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::Inputs,
"InputAudioMonitorTypeChanged", eventData);
} }
/** /**
@ -398,5 +417,6 @@ void EventHandler::HandleInputVolumeMeters(std::vector<json> inputs)
{ {
json eventData; json eventData;
eventData["inputs"] = inputs; eventData["inputs"] = inputs;
BroadcastEvent(EventSubscription::InputVolumeMeters, "InputVolumeMeters", eventData); BroadcastEvent(EventSubscription::InputVolumeMeters,
"InputVolumeMeters", eventData);
} }

View File

@ -19,11 +19,14 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #include "EventHandler.h"
#define CASE(x) case x: return #x; #define CASE(x) \
case x: \
return #x;
std::string GetMediaInputActionString(ObsMediaInputAction action) { std::string GetMediaInputActionString(ObsMediaInputAction action)
{
switch (action) { switch (action) {
default: default:
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE) CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE)
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY) CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY)
CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART) CASE(OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART)
@ -35,7 +38,7 @@ std::string GetMediaInputActionString(ObsMediaInputAction action) {
void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -44,12 +47,13 @@ void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return; return;
eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE); eventHandler->HandleMediaInputActionTriggered(
source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE);
} }
void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -58,12 +62,13 @@ void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return; return;
eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY); eventHandler->HandleMediaInputActionTriggered(
source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY);
} }
void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -72,12 +77,13 @@ void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return; return;
eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART); eventHandler->HandleMediaInputActionTriggered(
source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART);
} }
void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -86,12 +92,13 @@ void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return; return;
eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP); eventHandler->HandleMediaInputActionTriggered(
source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP);
} }
void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -100,12 +107,14 @@ void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return; return;
eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT); eventHandler->HandleMediaInputActionTriggered(
source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT);
} }
void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaPreviousMultiHandler(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -114,7 +123,8 @@ void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return; return;
eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS); eventHandler->HandleMediaInputActionTriggered(
source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS);
} }
/** /**
@ -130,9 +140,10 @@ void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data
* @api events * @api events
* @category media inputs * @category media inputs
*/ */
void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data) void EventHandler::HandleMediaInputPlaybackStarted(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -143,7 +154,8 @@ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventHandler->BroadcastEvent(EventSubscription::MediaInputs, "MediaInputPlaybackStarted", eventData); eventHandler->BroadcastEvent(EventSubscription::MediaInputs,
"MediaInputPlaybackStarted", eventData);
} }
/** /**
@ -161,7 +173,7 @@ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data
*/ */
void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data) void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -172,7 +184,8 @@ void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data)
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventHandler->BroadcastEvent(EventSubscription::MediaInputs, "MediaInputPlaybackEnded", eventData); eventHandler->BroadcastEvent(EventSubscription::MediaInputs,
"MediaInputPlaybackEnded", eventData);
} }
/** /**
@ -189,10 +202,12 @@ void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data)
* @api events * @api events
* @category media inputs * @category media inputs
*/ */
void EventHandler::HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action) void EventHandler::HandleMediaInputActionTriggered(obs_source_t *source,
ObsMediaInputAction action)
{ {
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["mediaAction"] = GetMediaInputActionString(action); eventData["mediaAction"] = GetMediaInputActionString(action);
BroadcastEvent(EventSubscription::MediaInputs, "MediaInputActionTriggered", eventData); BroadcastEvent(EventSubscription::MediaInputs,
"MediaInputActionTriggered", eventData);
} }

View File

@ -19,18 +19,19 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #include "EventHandler.h"
static bool GetOutputStateActive(ObsOutputState state) { static bool GetOutputStateActive(ObsOutputState state)
switch(state) { {
case OBS_WEBSOCKET_OUTPUT_STARTED: switch (state) {
case OBS_WEBSOCKET_OUTPUT_RESUMED: case OBS_WEBSOCKET_OUTPUT_STARTED:
return true; case OBS_WEBSOCKET_OUTPUT_RESUMED:
case OBS_WEBSOCKET_OUTPUT_STARTING: return true;
case OBS_WEBSOCKET_OUTPUT_STOPPING: case OBS_WEBSOCKET_OUTPUT_STARTING:
case OBS_WEBSOCKET_OUTPUT_STOPPED: case OBS_WEBSOCKET_OUTPUT_STOPPING:
case OBS_WEBSOCKET_OUTPUT_PAUSED: case OBS_WEBSOCKET_OUTPUT_STOPPED:
return false; case OBS_WEBSOCKET_OUTPUT_PAUSED:
default: return false;
return false; default:
return false;
} }
} }
@ -52,8 +53,10 @@ void EventHandler::HandleStreamStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); eventData["outputState"] =
BroadcastEvent(EventSubscription::Outputs, "StreamStateChanged", eventData); Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "StreamStateChanged",
eventData);
} }
/** /**
@ -74,8 +77,10 @@ void EventHandler::HandleRecordStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); eventData["outputState"] =
BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged", eventData); Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged",
eventData);
} }
/** /**
@ -96,8 +101,10 @@ void EventHandler::HandleReplayBufferStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); eventData["outputState"] =
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferStateChanged", eventData); Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferStateChanged",
eventData);
} }
/** /**
@ -118,8 +125,10 @@ void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); eventData["outputState"] =
BroadcastEvent(EventSubscription::Outputs, "VirtualcamStateChanged", eventData); Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "VirtualcamStateChanged",
eventData);
} }
/** /**
@ -138,6 +147,8 @@ void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state)
void EventHandler::HandleReplayBufferSaved() void EventHandler::HandleReplayBufferSaved()
{ {
json eventData; json eventData;
eventData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFilePath(); eventData["savedReplayPath"] =
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferSaved", eventData); Utils::Obs::StringHelper::GetLastReplayBufferFilePath();
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferSaved",
eventData);
} }

View File

@ -37,22 +37,27 @@ with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data) void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
return; return;
obs_sceneitem_t *sceneItem = GetCalldataPointer<obs_sceneitem_t>(data, "item"); obs_sceneitem_t *sceneItem =
GetCalldataPointer<obs_sceneitem_t>(data, "item");
if (!sceneItem) if (!sceneItem)
return; return;
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneName"] =
eventData["sourceName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); obs_source_get_name(obs_scene_get_source(scene));
eventData["sourceName"] =
obs_source_get_name(obs_sceneitem_get_source(sceneItem));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem); eventData["sceneItemIndex"] =
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemCreated", eventData); obs_sceneitem_get_order_position(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItems,
"SceneItemCreated", eventData);
} }
/** /**
@ -74,21 +79,25 @@ void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data) void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
return; return;
obs_sceneitem_t *sceneItem = GetCalldataPointer<obs_sceneitem_t>(data, "item"); obs_sceneitem_t *sceneItem =
GetCalldataPointer<obs_sceneitem_t>(data, "item");
if (!sceneItem) if (!sceneItem)
return; return;
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneName"] =
eventData["sourceName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); obs_source_get_name(obs_scene_get_source(scene));
eventData["sourceName"] =
obs_source_get_name(obs_sceneitem_get_source(sceneItem));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemRemoved", eventData); eventHandler->BroadcastEvent(EventSubscription::SceneItems,
"SceneItemRemoved", eventData);
} }
/** /**
@ -107,16 +116,19 @@ void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data) void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
return; return;
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneName"] =
eventData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(scene, true); obs_source_get_name(obs_scene_get_source(scene));
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemListReindexed", eventData); eventData["sceneItems"] =
Utils::Obs::ArrayHelper::GetSceneItemList(scene, true);
eventHandler->BroadcastEvent(EventSubscription::SceneItems,
"SceneItemListReindexed", eventData);
} }
/** /**
@ -134,25 +146,29 @@ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
* @api events * @api events
* @category scene items * @category scene items
*/ */
void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *data) void EventHandler::HandleSceneItemEnableStateChanged(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
return; return;
obs_sceneitem_t *sceneItem = GetCalldataPointer<obs_sceneitem_t>(data, "item"); obs_sceneitem_t *sceneItem =
GetCalldataPointer<obs_sceneitem_t>(data, "item");
if (!sceneItem) if (!sceneItem)
return; return;
bool sceneItemEnabled = calldata_bool(data, "visible"); bool sceneItemEnabled = calldata_bool(data, "visible");
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneName"] =
obs_source_get_name(obs_scene_get_source(scene));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemEnabled"] = sceneItemEnabled; eventData["sceneItemEnabled"] = sceneItemEnabled;
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemEnableStateChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::SceneItems,
"SceneItemEnableStateChanged", eventData);
} }
/** /**
@ -170,25 +186,29 @@ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *da
* @api events * @api events
* @category scene items * @category scene items
*/ */
void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data) void EventHandler::HandleSceneItemLockStateChanged(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
return; return;
obs_sceneitem_t *sceneItem = GetCalldataPointer<obs_sceneitem_t>(data, "item"); obs_sceneitem_t *sceneItem =
GetCalldataPointer<obs_sceneitem_t>(data, "item");
if (!sceneItem) if (!sceneItem)
return; return;
bool sceneItemLocked = calldata_bool(data, "locked"); bool sceneItemLocked = calldata_bool(data, "locked");
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneName"] =
obs_source_get_name(obs_scene_get_source(scene));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemLocked"] = sceneItemLocked; eventData["sceneItemLocked"] = sceneItemLocked;
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemLockStateChanged", eventData); eventHandler->BroadcastEvent(EventSubscription::SceneItems,
"SceneItemLockStateChanged", eventData);
} }
/** /**
@ -207,20 +227,23 @@ void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data
*/ */
void EventHandler::HandleSceneItemSelected(void *param, calldata_t *data) void EventHandler::HandleSceneItemSelected(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
return; return;
obs_sceneitem_t *sceneItem = GetCalldataPointer<obs_sceneitem_t>(data, "item"); obs_sceneitem_t *sceneItem =
GetCalldataPointer<obs_sceneitem_t>(data, "item");
if (!sceneItem) if (!sceneItem)
return; return;
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneName"] =
obs_source_get_name(obs_scene_get_source(scene));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemSelected", eventData); eventHandler->BroadcastEvent(EventSubscription::SceneItems,
"SceneItemSelected", eventData);
} }
/** /**
@ -238,9 +261,10 @@ void EventHandler::HandleSceneItemSelected(void *param, calldata_t *data)
* @api events * @api events
* @category scene items * @category scene items
*/ */
void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data) void EventHandler::HandleSceneItemTransformChanged(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
if (!eventHandler->_sceneItemTransformChangedRef.load()) if (!eventHandler->_sceneItemTransformChangedRef.load())
return; return;
@ -249,13 +273,18 @@ void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data
if (!scene) if (!scene)
return; return;
obs_sceneitem_t *sceneItem = GetCalldataPointer<obs_sceneitem_t>(data, "item"); obs_sceneitem_t *sceneItem =
GetCalldataPointer<obs_sceneitem_t>(data, "item");
if (!sceneItem) if (!sceneItem)
return; return;
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneName"] =
obs_source_get_name(obs_scene_get_source(scene));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemTransform"] = Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem); eventData["sceneItemTransform"] =
eventHandler->BroadcastEvent(EventSubscription::SceneItemTransformChanged, "SceneItemTransformChanged", eventData); Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem);
eventHandler->BroadcastEvent(
EventSubscription::SceneItemTransformChanged,
"SceneItemTransformChanged", eventData);
} }

View File

@ -77,12 +77,15 @@ void EventHandler::HandleSceneRemoved(obs_source_t *source)
* @api events * @api events
* @category scenes * @category scenes
*/ */
void EventHandler::HandleSceneNameChanged(obs_source_t *, std::string oldSceneName, std::string sceneName) void EventHandler::HandleSceneNameChanged(obs_source_t *,
std::string oldSceneName,
std::string sceneName)
{ {
json eventData; json eventData;
eventData["oldSceneName"] = oldSceneName; eventData["oldSceneName"] = oldSceneName;
eventData["sceneName"] = sceneName; eventData["sceneName"] = sceneName;
BroadcastEvent(EventSubscription::Scenes, "SceneNameChanged", eventData); BroadcastEvent(EventSubscription::Scenes, "SceneNameChanged",
eventData);
} }
/** /**
@ -104,7 +107,8 @@ void EventHandler::HandleCurrentProgramSceneChanged()
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(currentScene); eventData["sceneName"] = obs_source_get_name(currentScene);
BroadcastEvent(EventSubscription::Scenes, "CurrentProgramSceneChanged", eventData); BroadcastEvent(EventSubscription::Scenes, "CurrentProgramSceneChanged",
eventData);
} }
/** /**
@ -122,15 +126,17 @@ void EventHandler::HandleCurrentProgramSceneChanged()
*/ */
void EventHandler::HandleCurrentPreviewSceneChanged() void EventHandler::HandleCurrentPreviewSceneChanged()
{ {
OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene(); OBSSourceAutoRelease currentPreviewScene =
obs_frontend_get_current_preview_scene();
// This event may be called when OBS is not in studio mode, however retreiving the source while not in studio mode will return null. // This event may be called when OBS is not in studio mode, however retreiving the source while not in studio mode will return null.
if (!currentPreviewScene) if (!currentPreviewScene)
return; return;
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(currentPreviewScene); eventData["sceneName"] = obs_source_get_name(currentPreviewScene);
BroadcastEvent(EventSubscription::Scenes, "CurrentPreviewSceneChanged", eventData); BroadcastEvent(EventSubscription::Scenes, "CurrentPreviewSceneChanged",
eventData);
} }
/** /**
@ -152,5 +158,6 @@ void EventHandler::HandleSceneListChanged()
{ {
json eventData; json eventData;
eventData["scenes"] = Utils::Obs::ArrayHelper::GetSceneList(); eventData["scenes"] = Utils::Obs::ArrayHelper::GetSceneList();
BroadcastEvent(EventSubscription::Scenes, "SceneListChanged", eventData); BroadcastEvent(EventSubscription::Scenes, "SceneListChanged",
eventData);
} }

View File

@ -38,7 +38,8 @@ void EventHandler::HandleCurrentSceneTransitionChanged()
json eventData; json eventData;
eventData["transitionName"] = obs_source_get_name(transition); eventData["transitionName"] = obs_source_get_name(transition);
BroadcastEvent(EventSubscription::Transitions, "CurrentSceneTransitionChanged", eventData); BroadcastEvent(EventSubscription::Transitions,
"CurrentSceneTransitionChanged", eventData);
} }
/** /**
@ -57,8 +58,10 @@ void EventHandler::HandleCurrentSceneTransitionChanged()
void EventHandler::HandleCurrentSceneTransitionDurationChanged() void EventHandler::HandleCurrentSceneTransitionDurationChanged()
{ {
json eventData; json eventData;
eventData["transitionDuration"] = obs_frontend_get_transition_duration(); eventData["transitionDuration"] =
BroadcastEvent(EventSubscription::Transitions, "CurrentSceneTransitionDurationChanged", eventData); obs_frontend_get_transition_duration();
BroadcastEvent(EventSubscription::Transitions,
"CurrentSceneTransitionDurationChanged", eventData);
} }
/** /**
@ -76,7 +79,7 @@ void EventHandler::HandleCurrentSceneTransitionDurationChanged()
*/ */
void EventHandler::HandleSceneTransitionStarted(void *param, calldata_t *data) void EventHandler::HandleSceneTransitionStarted(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -84,7 +87,8 @@ void EventHandler::HandleSceneTransitionStarted(void *param, calldata_t *data)
json eventData; json eventData;
eventData["transitionName"] = obs_source_get_name(source); eventData["transitionName"] = obs_source_get_name(source);
eventHandler->BroadcastEvent(EventSubscription::Transitions, "SceneTransitionStarted", eventData); eventHandler->BroadcastEvent(EventSubscription::Transitions,
"SceneTransitionStarted", eventData);
} }
/** /**
@ -104,7 +108,7 @@ void EventHandler::HandleSceneTransitionStarted(void *param, calldata_t *data)
*/ */
void EventHandler::HandleSceneTransitionEnded(void *param, calldata_t *data) void EventHandler::HandleSceneTransitionEnded(void *param, calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -112,7 +116,8 @@ void EventHandler::HandleSceneTransitionEnded(void *param, calldata_t *data)
json eventData; json eventData;
eventData["transitionName"] = obs_source_get_name(source); eventData["transitionName"] = obs_source_get_name(source);
eventHandler->BroadcastEvent(EventSubscription::Transitions, "SceneTransitionEnded", eventData); eventHandler->BroadcastEvent(EventSubscription::Transitions,
"SceneTransitionEnded", eventData);
} }
/** /**
@ -133,9 +138,10 @@ void EventHandler::HandleSceneTransitionEnded(void *param, calldata_t *data)
* @api events * @api events
* @category transitions * @category transitions
*/ */
void EventHandler::HandleSceneTransitionVideoEnded(void *param, calldata_t *data) void EventHandler::HandleSceneTransitionVideoEnded(void *param,
calldata_t *data)
{ {
auto eventHandler = static_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler *>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -143,5 +149,6 @@ void EventHandler::HandleSceneTransitionVideoEnded(void *param, calldata_t *data
json eventData; json eventData;
eventData["transitionName"] = obs_source_get_name(source); eventData["transitionName"] = obs_source_get_name(source);
eventHandler->BroadcastEvent(EventSubscription::Transitions, "SceneTransitionVideoEnded", eventData); eventHandler->BroadcastEvent(EventSubscription::Transitions,
"SceneTransitionVideoEnded", eventData);
} }

View File

@ -36,5 +36,6 @@ void EventHandler::HandleStudioModeStateChanged(bool enabled)
{ {
json eventData; json eventData;
eventData["studioModeEnabled"] = enabled; eventData["studioModeEnabled"] = enabled;
BroadcastEvent(EventSubscription::Ui, "StudioModeStateChanged", eventData); BroadcastEvent(EventSubscription::Ui, "StudioModeStateChanged",
eventData);
} }

View File

@ -20,8 +20,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once #pragma once
namespace EventSubscription { namespace EventSubscription {
enum EventSubscription { enum EventSubscription {
/** /**
* Subcription value used to disable all events. * Subcription value used to disable all events.
* *
* @enumIdentifier None * @enumIdentifier None
@ -31,8 +31,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
None = 0, None = 0,
/** /**
* Subscription value to receive events in the `General` category. * Subscription value to receive events in the `General` category.
* *
* @enumIdentifier General * @enumIdentifier General
@ -42,8 +42,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
General = (1 << 0), General = (1 << 0),
/** /**
* Subscription value to receive events in the `Config` category. * Subscription value to receive events in the `Config` category.
* *
* @enumIdentifier Config * @enumIdentifier Config
@ -53,8 +53,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Config = (1 << 1), Config = (1 << 1),
/** /**
* Subscription value to receive events in the `Scenes` category. * Subscription value to receive events in the `Scenes` category.
* *
* @enumIdentifier Scenes * @enumIdentifier Scenes
@ -64,8 +64,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Scenes = (1 << 2), Scenes = (1 << 2),
/** /**
* Subscription value to receive events in the `Inputs` category. * Subscription value to receive events in the `Inputs` category.
* *
* @enumIdentifier Inputs * @enumIdentifier Inputs
@ -75,8 +75,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Inputs = (1 << 3), Inputs = (1 << 3),
/** /**
* Subscription value to receive events in the `Transitions` category. * Subscription value to receive events in the `Transitions` category.
* *
* @enumIdentifier Transitions * @enumIdentifier Transitions
@ -86,8 +86,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Transitions = (1 << 4), Transitions = (1 << 4),
/** /**
* Subscription value to receive events in the `Filters` category. * Subscription value to receive events in the `Filters` category.
* *
* @enumIdentifier Filters * @enumIdentifier Filters
@ -97,8 +97,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Filters = (1 << 5), Filters = (1 << 5),
/** /**
* Subscription value to receive events in the `Outputs` category. * Subscription value to receive events in the `Outputs` category.
* *
* @enumIdentifier Outputs * @enumIdentifier Outputs
@ -108,8 +108,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Outputs = (1 << 6), Outputs = (1 << 6),
/** /**
* Subscription value to receive events in the `SceneItems` category. * Subscription value to receive events in the `SceneItems` category.
* *
* @enumIdentifier SceneItems * @enumIdentifier SceneItems
@ -119,8 +119,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
SceneItems = (1 << 7), SceneItems = (1 << 7),
/** /**
* Subscription value to receive events in the `MediaInputs` category. * Subscription value to receive events in the `MediaInputs` category.
* *
* @enumIdentifier MediaInputs * @enumIdentifier MediaInputs
@ -130,8 +130,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
MediaInputs = (1 << 8), MediaInputs = (1 << 8),
/** /**
* Subscription value to receive the `VendorEvent` event. * Subscription value to receive the `VendorEvent` event.
* *
* @enumIdentifier Vendors * @enumIdentifier Vendors
@ -141,8 +141,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Vendors = (1 << 9), Vendors = (1 << 9),
/** /**
* Subscription value to receive events in the `Ui` category. * Subscription value to receive events in the `Ui` category.
* *
* @enumIdentifier Ui * @enumIdentifier Ui
@ -152,8 +152,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Ui = (1 << 10), Ui = (1 << 10),
/** /**
* Helper to receive all non-high-volume events. * Helper to receive all non-high-volume events.
* *
* @enumIdentifier All * @enumIdentifier All
@ -163,8 +163,9 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Ui | Vendors), All = (General | Config | Scenes | Inputs | Transitions | Filters |
/** Outputs | SceneItems | MediaInputs | Ui | Vendors),
/**
* Subscription value to receive the `InputVolumeMeters` high-volume event. * Subscription value to receive the `InputVolumeMeters` high-volume event.
* *
* @enumIdentifier InputVolumeMeters * @enumIdentifier InputVolumeMeters
@ -174,8 +175,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InputVolumeMeters = (1 << 16), InputVolumeMeters = (1 << 16),
/** /**
* Subscription value to receive the `InputActiveStateChanged` high-volume event. * Subscription value to receive the `InputActiveStateChanged` high-volume event.
* *
* @enumIdentifier InputActiveStateChanged * @enumIdentifier InputActiveStateChanged
@ -185,8 +186,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InputActiveStateChanged = (1 << 17), InputActiveStateChanged = (1 << 17),
/** /**
* Subscription value to receive the `InputShowStateChanged` high-volume event. * Subscription value to receive the `InputShowStateChanged` high-volume event.
* *
* @enumIdentifier InputShowStateChanged * @enumIdentifier InputShowStateChanged
@ -196,8 +197,8 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InputShowStateChanged = (1 << 18), InputShowStateChanged = (1 << 18),
/** /**
* Subscription value to receive the `SceneItemTransformChanged` high-volume event. * Subscription value to receive the `SceneItemTransformChanged` high-volume event.
* *
* @enumIdentifier SceneItemTransformChanged * @enumIdentifier SceneItemTransformChanged
@ -207,6 +208,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
SceneItemTransformChanged = (1 << 19), SceneItemTransformChanged = (1 << 19),
}; };
} }

View File

@ -28,18 +28,17 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../Config.h" #include "../Config.h"
#include "../utils/Platform.h" #include "../utils/Platform.h"
ConnectInfo::ConnectInfo(QWidget* parent) : ConnectInfo::ConnectInfo(QWidget *parent)
QDialog(parent, Qt::Dialog), : QDialog(parent, Qt::Dialog), ui(new Ui::ConnectInfo)
ui(new Ui::ConnectInfo)
{ {
ui->setupUi(this); ui->setupUi(this);
connect(ui->copyServerIpButton, &QPushButton::clicked, connect(ui->copyServerIpButton, &QPushButton::clicked, this,
this, &ConnectInfo::CopyServerIpButtonClicked); &ConnectInfo::CopyServerIpButtonClicked);
connect(ui->copyServerPortButton, &QPushButton::clicked, connect(ui->copyServerPortButton, &QPushButton::clicked, this,
this, &ConnectInfo::CopyServerPortButtonClicked); &ConnectInfo::CopyServerPortButtonClicked);
connect(ui->copyServerPasswordButton, &QPushButton::clicked, connect(ui->copyServerPasswordButton, &QPushButton::clicked, this,
this, &ConnectInfo::CopyServerPasswordButtonClicked); &ConnectInfo::CopyServerPasswordButtonClicked);
} }
ConnectInfo::~ConnectInfo() ConnectInfo::~ConnectInfo()
@ -56,11 +55,13 @@ void ConnectInfo::RefreshData()
{ {
auto conf = GetConfig(); auto conf = GetConfig();
if (!conf) { if (!conf) {
blog(LOG_ERROR, "[ConnectInfo::showEvent] Unable to retreive config!"); blog(LOG_ERROR,
"[ConnectInfo::showEvent] Unable to retreive config!");
return; return;
} }
QString serverIp = QString::fromStdString(Utils::Platform::GetLocalAddress()); QString serverIp =
QString::fromStdString(Utils::Platform::GetLocalAddress());
ui->serverIpLineEdit->setText(serverIp); ui->serverIpLineEdit->setText(serverIp);
QString serverPort = QString::number(conf->ServerPort); QString serverPort = QString::number(conf->ServerPort);
@ -72,15 +73,20 @@ void ConnectInfo::RefreshData()
serverPassword = QUrl::toPercentEncoding(conf->ServerPassword); serverPassword = QUrl::toPercentEncoding(conf->ServerPassword);
} else { } else {
ui->copyServerPasswordButton->setEnabled(false); ui->copyServerPasswordButton->setEnabled(false);
serverPassword = obs_module_text("OBSWebSocket.ConnectInfo.ServerPasswordPlaceholderText"); serverPassword = obs_module_text(
"OBSWebSocket.ConnectInfo.ServerPasswordPlaceholderText");
} }
ui->serverPasswordLineEdit->setText(serverPassword); ui->serverPasswordLineEdit->setText(serverPassword);
QString connectString; QString connectString;
if (conf->AuthRequired) if (conf->AuthRequired)
connectString = QString("obsws://%1:%2/%3").arg(serverIp).arg(serverPort).arg(serverPassword); connectString = QString("obsws://%1:%2/%3")
.arg(serverIp)
.arg(serverPort)
.arg(serverPassword);
else else
connectString = QString("obsws://%1:%2").arg(serverIp).arg(serverPort); connectString =
QString("obsws://%1:%2").arg(serverIp).arg(serverPort);
DrawQr(connectString); DrawQr(connectString);
} }
@ -113,14 +119,15 @@ void ConnectInfo::DrawQr(QString qrText)
QPixmap map(230, 230); QPixmap map(230, 230);
map.fill(Qt::white); map.fill(Qt::white);
QPainter painter(&map); QPainter painter(&map);
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(QT_TO_UTF8(qrText), qrcodegen::QrCode::Ecc::MEDIUM); qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(
QT_TO_UTF8(qrText), qrcodegen::QrCode::Ecc::MEDIUM);
const int s = qr.getSize() > 0 ? qr.getSize() : 1; const int s = qr.getSize() > 0 ? qr.getSize() : 1;
const double w = map.width(); const double w = map.width();
const double h = map.height(); const double h = map.height();
const double aspect = w/h; const double aspect = w / h;
const double size = ((aspect > 1.0) ? h : w); const double size = ((aspect > 1.0) ? h : w);
const double scale = size / (s+2); const double scale = size / (s + 2);
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
painter.setBrush(Qt::black); painter.setBrush(Qt::black);

View File

@ -25,12 +25,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "ui_ConnectInfo.h" #include "ui_ConnectInfo.h"
class ConnectInfo : public QDialog class ConnectInfo : public QDialog {
{
Q_OBJECT Q_OBJECT
public: public:
explicit ConnectInfo(QWidget* parent = 0); explicit ConnectInfo(QWidget *parent = 0);
~ConnectInfo(); ~ConnectInfo();
void showEvent(QShowEvent *event); void showEvent(QShowEvent *event);
void RefreshData(); void RefreshData();

View File

@ -32,20 +32,23 @@ with this program. If not, see <https://www.gnu.org/licenses/>
QString GetToolTipIconHtml() QString GetToolTipIconHtml()
{ {
bool lightTheme = QApplication::palette().text().color().redF() < 0.5; bool lightTheme = QApplication::palette().text().color().redF() < 0.5;
QString iconFile = lightTheme ? ":toolTip/images/help.svg" : ":toolTip/images/help_light.svg"; QString iconFile = lightTheme ? ":toolTip/images/help.svg"
QString iconTemplate = "<html> <img src='%1' style=' vertical-align: bottom; ' /></html>"; : ":toolTip/images/help_light.svg";
QString iconTemplate =
"<html> <img src='%1' style=' vertical-align: bottom; ' /></html>";
return iconTemplate.arg(iconFile); return iconTemplate.arg(iconFile);
} }
SettingsDialog::SettingsDialog(QWidget* parent) : SettingsDialog::SettingsDialog(QWidget *parent)
QDialog(parent, Qt::Dialog), : QDialog(parent, Qt::Dialog),
ui(new Ui::SettingsDialog), ui(new Ui::SettingsDialog),
connectInfo(new ConnectInfo), connectInfo(new ConnectInfo),
sessionTableTimer(new QTimer), sessionTableTimer(new QTimer),
passwordManuallyEdited(false) passwordManuallyEdited(false)
{ {
ui->setupUi(this); ui->setupUi(this);
ui->websocketSessionTable->horizontalHeader()->resizeSection(3, 100); // Resize Session Table column widths ui->websocketSessionTable->horizontalHeader()->resizeSection(
3, 100); // Resize Session Table column widths
ui->websocketSessionTable->horizontalHeader()->resizeSection(4, 100); ui->websocketSessionTable->horizontalHeader()->resizeSection(4, 100);
// Remove the ? button on dialogs on Windows // Remove the ? button on dialogs on Windows
@ -56,18 +59,18 @@ SettingsDialog::SettingsDialog(QWidget* parent) :
ui->enableDebugLoggingToolTipLabel->setText(toolTipHtml); ui->enableDebugLoggingToolTipLabel->setText(toolTipHtml);
ui->allowExternalToolTipLabel->setText(toolTipHtml); ui->allowExternalToolTipLabel->setText(toolTipHtml);
connect(sessionTableTimer, &QTimer::timeout, connect(sessionTableTimer, &QTimer::timeout, this,
this, &SettingsDialog::FillSessionTable); &SettingsDialog::FillSessionTable);
connect(ui->buttonBox, &QDialogButtonBox::clicked, connect(ui->buttonBox, &QDialogButtonBox::clicked, this,
this, &SettingsDialog::DialogButtonClicked); &SettingsDialog::DialogButtonClicked);
connect(ui->enableAuthenticationCheckBox, &QCheckBox::stateChanged, connect(ui->enableAuthenticationCheckBox, &QCheckBox::stateChanged,
this, &SettingsDialog::EnableAuthenticationCheckBoxChanged); this, &SettingsDialog::EnableAuthenticationCheckBoxChanged);
connect(ui->generatePasswordButton, &QPushButton::clicked, connect(ui->generatePasswordButton, &QPushButton::clicked, this,
this, &SettingsDialog::GeneratePasswordButtonClicked); &SettingsDialog::GeneratePasswordButtonClicked);
connect(ui->showConnectInfoButton, &QPushButton::clicked, connect(ui->showConnectInfoButton, &QPushButton::clicked, this,
this, &SettingsDialog::ShowConnectInfoButtonClicked); &SettingsDialog::ShowConnectInfoButtonClicked);
connect(ui->serverPasswordLineEdit, &QLineEdit::textEdited, connect(ui->serverPasswordLineEdit, &QLineEdit::textEdited, this,
this, &SettingsDialog::PasswordEdited); &SettingsDialog::PasswordEdited);
} }
SettingsDialog::~SettingsDialog() SettingsDialog::~SettingsDialog()
@ -81,7 +84,8 @@ void SettingsDialog::showEvent(QShowEvent *)
{ {
auto conf = GetConfig(); auto conf = GetConfig();
if (!conf) { if (!conf) {
blog(LOG_ERROR, "[SettingsDialog::showEvent] Unable to retreive config!"); blog(LOG_ERROR,
"[SettingsDialog::showEvent] Unable to retreive config!");
return; return;
} }
@ -121,7 +125,8 @@ void SettingsDialog::RefreshData()
{ {
auto conf = GetConfig(); auto conf = GetConfig();
if (!conf) { if (!conf) {
blog(LOG_ERROR, "[SettingsDialog::RefreshData] Unable to retreive config!"); blog(LOG_ERROR,
"[SettingsDialog::RefreshData] Unable to retreive config!");
return; return;
} }
@ -137,7 +142,11 @@ void SettingsDialog::RefreshData()
ui->serverPasswordLineEdit->setEnabled(conf->AuthRequired); ui->serverPasswordLineEdit->setEnabled(conf->AuthRequired);
ui->generatePasswordButton->setEnabled(conf->AuthRequired); ui->generatePasswordButton->setEnabled(conf->AuthRequired);
ui->showConnectInfoButton->setToolTip(ui->allowExternalCheckBox->isChecked() ? "" : obs_module_text("OBSWebSocket.Settings.ShowConnectInfoHoverText")); ui->showConnectInfoButton->setToolTip(
ui->allowExternalCheckBox->isChecked()
? ""
: obs_module_text(
"OBSWebSocket.Settings.ShowConnectInfoHoverText"));
FillSessionTable(); FillSessionTable();
} }
@ -155,43 +164,55 @@ void SettingsDialog::SaveFormData()
{ {
auto conf = GetConfig(); auto conf = GetConfig();
if (!conf) { if (!conf) {
blog(LOG_ERROR, "[SettingsDialog::SaveFormData] Unable to retreive config!"); blog(LOG_ERROR,
"[SettingsDialog::SaveFormData] Unable to retreive config!");
return; return;
} }
if (ui->serverPasswordLineEdit->text().length() < 6) { if (ui->serverPasswordLineEdit->text().length() < 6) {
QMessageBox msgBox; QMessageBox msgBox;
msgBox.setWindowTitle(obs_module_text("OBSWebSocket.Settings.Save.PasswordInvalidErrorTitle")); msgBox.setWindowTitle(obs_module_text(
msgBox.setText(obs_module_text("OBSWebSocket.Settings.Save.PasswordInvalidErrorMessage")); "OBSWebSocket.Settings.Save.PasswordInvalidErrorTitle"));
msgBox.setText(obs_module_text(
"OBSWebSocket.Settings.Save.PasswordInvalidErrorMessage"));
msgBox.setStandardButtons(QMessageBox::Ok); msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.exec(); msgBox.exec();
return; return;
} }
// Show a confirmation box to the user if they attempt to provide their own password // Show a confirmation box to the user if they attempt to provide their own password
if (passwordManuallyEdited && (conf->ServerPassword != ui->serverPasswordLineEdit->text())) { if (passwordManuallyEdited &&
(conf->ServerPassword != ui->serverPasswordLineEdit->text())) {
QMessageBox msgBox; QMessageBox msgBox;
msgBox.setWindowTitle(obs_module_text("OBSWebSocket.Settings.Save.UserPasswordWarningTitle")); msgBox.setWindowTitle(obs_module_text(
msgBox.setText(obs_module_text("OBSWebSocket.Settings.Save.UserPasswordWarningMessage")); "OBSWebSocket.Settings.Save.UserPasswordWarningTitle"));
msgBox.setInformativeText(obs_module_text("OBSWebSocket.Settings.Save.UserPasswordWarningInfoText")); msgBox.setText(obs_module_text(
"OBSWebSocket.Settings.Save.UserPasswordWarningMessage"));
msgBox.setInformativeText(obs_module_text(
"OBSWebSocket.Settings.Save.UserPasswordWarningInfoText"));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No);
int ret = msgBox.exec(); int ret = msgBox.exec();
switch (ret) { switch (ret) {
case QMessageBox::Yes: case QMessageBox::Yes:
break; break;
case QMessageBox::No: case QMessageBox::No:
default: default:
ui->serverPasswordLineEdit->setText(conf->ServerPassword); ui->serverPasswordLineEdit->setText(
return; conf->ServerPassword);
return;
} }
} }
bool needsRestart = (conf->ServerEnabled != ui->enableWebSocketServerCheckBox->isChecked()) || bool needsRestart =
(ui->enableAuthenticationCheckBox->isChecked() && conf->ServerPassword != ui->serverPasswordLineEdit->text()) || (conf->ServerEnabled !=
(conf->BindLoopback == ui->allowExternalCheckBox->isChecked()) || ui->enableWebSocketServerCheckBox->isChecked()) ||
(conf->ServerPort != ui->serverPortSpinBox->value()); (ui->enableAuthenticationCheckBox->isChecked() &&
conf->ServerPassword != ui->serverPasswordLineEdit->text()) ||
(conf->BindLoopback ==
ui->allowExternalCheckBox->isChecked()) ||
(conf->ServerPort != ui->serverPortSpinBox->value());
conf->ServerEnabled = ui->enableWebSocketServerCheckBox->isChecked(); conf->ServerEnabled = ui->enableWebSocketServerCheckBox->isChecked();
conf->AlertsEnabled = ui->enableSystemTrayAlertsCheckBox->isChecked(); conf->AlertsEnabled = ui->enableSystemTrayAlertsCheckBox->isChecked();
@ -207,7 +228,8 @@ void SettingsDialog::SaveFormData()
connectInfo->RefreshData(); connectInfo->RefreshData();
if (needsRestart) { if (needsRestart) {
blog(LOG_INFO, "[SettingsDialog::SaveFormData] A setting was changed which requires a server restart."); blog(LOG_INFO,
"[SettingsDialog::SaveFormData] A setting was changed which requires a server restart.");
auto server = GetWebSocketServer(); auto server = GetWebSocketServer();
server->Stop(); server->Stop();
if (conf->ServerEnabled) { if (conf->ServerEnabled) {
@ -220,7 +242,8 @@ void SettingsDialog::FillSessionTable()
{ {
auto webSocketServer = GetWebSocketServer(); auto webSocketServer = GetWebSocketServer();
if (!webSocketServer) { if (!webSocketServer) {
blog(LOG_ERROR, "[SettingsDialog::FillSessionTable] Unable to fetch websocket server instance!"); blog(LOG_ERROR,
"[SettingsDialog::FillSessionTable] Unable to fetch websocket server instance!");
return; return;
} }
@ -234,19 +257,26 @@ void SettingsDialog::FillSessionTable()
QPixmap crossIconPixmap = crossIcon.pixmap(QSize(25, 25)); QPixmap crossIconPixmap = crossIcon.pixmap(QSize(25, 25));
// Todo: Make a util for translations so that we don't need to import a bunch of obs libraries in order to use them. // Todo: Make a util for translations so that we don't need to import a bunch of obs libraries in order to use them.
QString kickButtonText = obs_module_text("OBSWebSocket.SessionTable.KickButtonText"); QString kickButtonText =
obs_module_text("OBSWebSocket.SessionTable.KickButtonText");
ui->websocketSessionTable->setRowCount(rowCount); ui->websocketSessionTable->setRowCount(rowCount);
size_t i = 0; size_t i = 0;
for (auto session : webSocketSessions) { for (auto session : webSocketSessions) {
QTableWidgetItem *addressItem = new QTableWidgetItem(QString::fromStdString(session.remoteAddress)); QTableWidgetItem *addressItem = new QTableWidgetItem(
QString::fromStdString(session.remoteAddress));
ui->websocketSessionTable->setItem(i, 0, addressItem); ui->websocketSessionTable->setItem(i, 0, addressItem);
uint64_t sessionDuration = QDateTime::currentSecsSinceEpoch() - session.connectedAt; uint64_t sessionDuration = QDateTime::currentSecsSinceEpoch() -
QTableWidgetItem *durationItem = new QTableWidgetItem(QTime(0, 0, sessionDuration).toString("hh:mm:ss")); session.connectedAt;
QTableWidgetItem *durationItem = new QTableWidgetItem(
QTime(0, 0, sessionDuration).toString("hh:mm:ss"));
ui->websocketSessionTable->setItem(i, 1, durationItem); ui->websocketSessionTable->setItem(i, 1, durationItem);
QTableWidgetItem *statsItem = new QTableWidgetItem(QString("%1/%2").arg(session.incomingMessages).arg(session.outgoingMessages)); QTableWidgetItem *statsItem = new QTableWidgetItem(
QString("%1/%2")
.arg(session.incomingMessages)
.arg(session.outgoingMessages));
ui->websocketSessionTable->setItem(i, 2, statsItem); ui->websocketSessionTable->setItem(i, 2, statsItem);
QLabel *identifiedLabel = new QLabel(); QLabel *identifiedLabel = new QLabel();
@ -258,14 +288,17 @@ void SettingsDialog::FillSessionTable()
} }
ui->websocketSessionTable->setCellWidget(i, 3, identifiedLabel); ui->websocketSessionTable->setCellWidget(i, 3, identifiedLabel);
QPushButton *invalidateButton = new QPushButton(kickButtonText, this); QPushButton *invalidateButton =
new QPushButton(kickButtonText, this);
QWidget *invalidateButtonWidget = new QWidget(); QWidget *invalidateButtonWidget = new QWidget();
QHBoxLayout *invalidateButtonLayout = new QHBoxLayout(invalidateButtonWidget); QHBoxLayout *invalidateButtonLayout =
new QHBoxLayout(invalidateButtonWidget);
invalidateButtonLayout->addWidget(invalidateButton); invalidateButtonLayout->addWidget(invalidateButton);
invalidateButtonLayout->setAlignment(Qt::AlignCenter); invalidateButtonLayout->setAlignment(Qt::AlignCenter);
invalidateButtonLayout->setContentsMargins(0, 0, 0, 0); invalidateButtonLayout->setContentsMargins(0, 0, 0, 0);
invalidateButtonWidget->setLayout(invalidateButtonLayout); invalidateButtonWidget->setLayout(invalidateButtonLayout);
ui->websocketSessionTable->setCellWidget(i, 4, invalidateButtonWidget); ui->websocketSessionTable->setCellWidget(
i, 4, invalidateButtonWidget);
connect(invalidateButton, &QPushButton::clicked, [=]() { connect(invalidateButton, &QPushButton::clicked, [=]() {
webSocketServer->InvalidateSession(session.hdl); webSocketServer->InvalidateSession(session.hdl);
}); });
@ -287,7 +320,8 @@ void SettingsDialog::EnableAuthenticationCheckBoxChanged()
void SettingsDialog::GeneratePasswordButtonClicked() void SettingsDialog::GeneratePasswordButtonClicked()
{ {
QString newPassword = QString::fromStdString(Utils::Crypto::GeneratePassword()); QString newPassword =
QString::fromStdString(Utils::Crypto::GeneratePassword());
ui->serverPasswordLineEdit->setText(newPassword); ui->serverPasswordLineEdit->setText(newPassword);
ui->serverPasswordLineEdit->selectAll(); ui->serverPasswordLineEdit->selectAll();
passwordManuallyEdited = false; passwordManuallyEdited = false;
@ -297,19 +331,22 @@ void SettingsDialog::ShowConnectInfoButtonClicked()
{ {
if (obs_video_active()) { if (obs_video_active()) {
QMessageBox msgBox; QMessageBox msgBox;
msgBox.setWindowTitle(obs_module_text("OBSWebSocket.Settings.ShowConnectInfoWarningTitle")); msgBox.setWindowTitle(obs_module_text(
msgBox.setText(obs_module_text("OBSWebSocket.Settings.ShowConnectInfoWarningMessage")); "OBSWebSocket.Settings.ShowConnectInfoWarningTitle"));
msgBox.setInformativeText(obs_module_text("OBSWebSocket.Settings.ShowConnectInfoWarningInfoText")); msgBox.setText(obs_module_text(
"OBSWebSocket.Settings.ShowConnectInfoWarningMessage"));
msgBox.setInformativeText(obs_module_text(
"OBSWebSocket.Settings.ShowConnectInfoWarningInfoText"));
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No);
int ret = msgBox.exec(); int ret = msgBox.exec();
switch (ret) { switch (ret) {
case QMessageBox::Yes: case QMessageBox::Yes:
break; break;
case QMessageBox::No: case QMessageBox::No:
default: default:
return; return;
} }
} }

View File

@ -27,12 +27,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "ui_SettingsDialog.h" #include "ui_SettingsDialog.h"
class SettingsDialog : public QDialog class SettingsDialog : public QDialog {
{
Q_OBJECT Q_OBJECT
public: public:
explicit SettingsDialog(QWidget* parent = 0); explicit SettingsDialog(QWidget *parent = 0);
~SettingsDialog(); ~SettingsDialog();
void showEvent(QShowEvent *event); void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event); void hideEvent(QHideEvent *event);

View File

@ -32,23 +32,35 @@ with this program. If not, see <https://www.gnu.org/licenses/>
OBS_DECLARE_MODULE() OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
OBS_MODULE_AUTHOR("OBSProject") OBS_MODULE_AUTHOR("OBSProject")
const char *obs_module_name(void) { return "obs-websocket"; } const char *obs_module_name(void)
const char *obs_module_description(void) { return obs_module_text("OBSWebSocket.Plugin.Description"); } {
return "obs-websocket";
}
const char *obs_module_description(void)
{
return obs_module_text("OBSWebSocket.Plugin.Description");
}
os_cpu_usage_info_t* _cpuUsageInfo; os_cpu_usage_info_t *_cpuUsageInfo;
ConfigPtr _config; ConfigPtr _config;
EventHandlerPtr _eventHandler; EventHandlerPtr _eventHandler;
WebSocketApiPtr _webSocketApi; WebSocketApiPtr _webSocketApi;
WebSocketServerPtr _webSocketServer; WebSocketServerPtr _webSocketServer;
SettingsDialog *_settingsDialog = nullptr; SettingsDialog *_settingsDialog = nullptr;
void WebSocketApiEventCallback(std::string vendorName, std::string eventType, obs_data_t *obsEventData); void WebSocketApiEventCallback(std::string vendorName, std::string eventType,
obs_data_t *obsEventData);
bool obs_module_load(void) bool obs_module_load(void)
{ {
blog(LOG_INFO, "[obs_module_load] you can haz websockets (Version: %s | RPC Version: %d)", OBS_WEBSOCKET_VERSION, OBS_WEBSOCKET_RPC_VERSION); blog(LOG_INFO,
blog(LOG_INFO, "[obs_module_load] Qt version (compile-time): %s | Qt version (run-time): %s", QT_VERSION_STR, qVersion()); "[obs_module_load] you can haz websockets (Version: %s | RPC Version: %d)",
blog(LOG_INFO, "[obs_module_load] Linked ASIO Version: %d", ASIO_VERSION); OBS_WEBSOCKET_VERSION, OBS_WEBSOCKET_RPC_VERSION);
blog(LOG_INFO,
"[obs_module_load] Qt version (compile-time): %s | Qt version (run-time): %s",
QT_VERSION_STR, qVersion());
blog(LOG_INFO, "[obs_module_load] Linked ASIO Version: %d",
ASIO_VERSION);
// Initialize the cpu stats // Initialize the cpu stats
_cpuUsageInfo = os_cpu_usage_info_start(); _cpuUsageInfo = os_cpu_usage_info_start();
@ -69,14 +81,18 @@ bool obs_module_load(void)
// Initialize the settings dialog // Initialize the settings dialog
obs_frontend_push_ui_translation(obs_module_get_string); obs_frontend_push_ui_translation(obs_module_get_string);
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window()); QMainWindow *mainWindow =
static_cast<QMainWindow *>(obs_frontend_get_main_window());
_settingsDialog = new SettingsDialog(mainWindow); _settingsDialog = new SettingsDialog(mainWindow);
obs_frontend_pop_ui_translation(); obs_frontend_pop_ui_translation();
// Add the settings dialog to the tools menu // Add the settings dialog to the tools menu
const char* menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle"); const char *menuActionText =
QAction* menuAction = (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText); obs_module_text("OBSWebSocket.Settings.DialogTitle");
QObject::connect(menuAction, &QAction::triggered, [] { _settingsDialog->ToggleShowHide(); }); QAction *menuAction =
(QAction *)obs_frontend_add_tools_menu_qaction(menuActionText);
QObject::connect(menuAction, &QAction::triggered,
[] { _settingsDialog->ToggleShowHide(); });
blog(LOG_INFO, "[obs_module_load] Module loaded."); blog(LOG_INFO, "[obs_module_load] Module loaded.");
return true; return true;
@ -88,7 +104,8 @@ void obs_module_unload()
// Shutdown the WebSocket server if it is running // Shutdown the WebSocket server if it is running
if (_webSocketServer->IsListening()) { if (_webSocketServer->IsListening()) {
blog_debug("[obs_module_unload] WebSocket server is running. Stopping..."); blog_debug(
"[obs_module_unload] WebSocket server is running. Stopping...");
_webSocketServer->Stop(); _webSocketServer->Stop();
} }
@ -111,7 +128,7 @@ void obs_module_unload()
blog(LOG_INFO, "[obs_module_unload] Finished shutting down."); blog(LOG_INFO, "[obs_module_unload] Finished shutting down.");
} }
os_cpu_usage_info_t* GetCpuUsageInfo() os_cpu_usage_info_t *GetCpuUsageInfo()
{ {
return _cpuUsageInfo; return _cpuUsageInfo;
} }
@ -159,7 +176,8 @@ bool IsDebugEnabled()
* @api events * @api events
* @category general * @category general
*/ */
void WebSocketApiEventCallback(std::string vendorName, std::string eventType, obs_data_t *obsEventData) void WebSocketApiEventCallback(std::string vendorName, std::string eventType,
obs_data_t *obsEventData)
{ {
json eventData = Utils::Json::ObsDataToJson(obsEventData); json eventData = Utils::Json::ObsDataToJson(obsEventData);
@ -168,16 +186,19 @@ void WebSocketApiEventCallback(std::string vendorName, std::string eventType, ob
broadcastEventData["eventType"] = eventType; broadcastEventData["eventType"] = eventType;
broadcastEventData["eventData"] = eventData; broadcastEventData["eventData"] = eventData;
_webSocketServer->BroadcastEvent(EventSubscription::Vendors, "VendorEvent", broadcastEventData); _webSocketServer->BroadcastEvent(EventSubscription::Vendors,
"VendorEvent", broadcastEventData);
} }
#ifdef PLUGIN_TESTS #ifdef PLUGIN_TESTS
static void test_vendor_request_cb(obs_data_t *requestData, obs_data_t *responseData, void *priv_data) static void test_vendor_request_cb(obs_data_t *requestData,
obs_data_t *responseData, void *priv_data)
{ {
blog(LOG_INFO, "[test_vendor_request_cb] Request called!"); blog(LOG_INFO, "[test_vendor_request_cb] Request called!");
blog(LOG_INFO, "[test_vendor_request_cb] Request data: %s", obs_data_get_json(requestData)); blog(LOG_INFO, "[test_vendor_request_cb] Request data: %s",
obs_data_get_json(requestData));
// Set an item to the response data // Set an item to the response data
obs_data_set_string(responseData, "test", "pp"); obs_data_set_string(responseData, "test", "pp");
@ -192,25 +213,34 @@ void obs_module_post_load()
// Test plugin API version fetch // Test plugin API version fetch
uint apiVersion = obs_websocket_get_api_version(); uint apiVersion = obs_websocket_get_api_version();
blog(LOG_INFO, "[obs_module_post_load] obs-websocket plugin API version: %u", apiVersion); blog(LOG_INFO,
"[obs_module_post_load] obs-websocket plugin API version: %u",
apiVersion);
// Test calling obs-websocket requests // Test calling obs-websocket requests
struct obs_websocket_request_response *response = obs_websocket_call_request("GetVersion"); struct obs_websocket_request_response *response =
obs_websocket_call_request("GetVersion");
if (response) { if (response) {
blog(LOG_INFO, "[obs_module_post_load] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s", response->status_code, response->comment, response->response_data); blog(LOG_INFO,
"[obs_module_post_load] Called GetVersion. Status Code: %u | Comment: %s | Response Data: %s",
response->status_code, response->comment,
response->response_data);
obs_websocket_request_response_free(response); obs_websocket_request_response_free(response);
} }
// Test vendor creation // Test vendor creation
auto vendor = obs_websocket_register_vendor("obs-websocket-test"); auto vendor = obs_websocket_register_vendor("obs-websocket-test");
if (!vendor) { if (!vendor) {
blog(LOG_WARNING, "[obs_module_post_load] Failed to create vendor!"); blog(LOG_WARNING,
"[obs_module_post_load] Failed to create vendor!");
return; return;
} }
// Test vendor request registration // Test vendor request registration
if (!obs_websocket_vendor_register_request(vendor, "TestRequest", test_vendor_request_cb, vendor)) { if (!obs_websocket_vendor_register_request(
blog(LOG_WARNING, "[obs_module_post_load] Failed to register vendor request!"); vendor, "TestRequest", test_vendor_request_cb, vendor)) {
blog(LOG_WARNING,
"[obs_module_post_load] Failed to register vendor request!");
return; return;
} }

View File

@ -38,7 +38,7 @@ typedef std::shared_ptr<WebSocketApi> WebSocketApiPtr;
class WebSocketServer; class WebSocketServer;
typedef std::shared_ptr<WebSocketServer> WebSocketServerPtr; typedef std::shared_ptr<WebSocketServer> WebSocketServerPtr;
os_cpu_usage_info_t* GetCpuUsageInfo(); os_cpu_usage_info_t *GetCpuUsageInfo();
ConfigPtr GetConfig(); ConfigPtr GetConfig();

View File

@ -24,8 +24,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../utils/Compat.h" #include "../utils/Compat.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
struct SerialFrameBatch struct SerialFrameBatch {
{
RequestHandler &requestHandler; RequestHandler &requestHandler;
std::queue<RequestBatchRequest> requests; std::queue<RequestBatchRequest> requests;
std::vector<RequestResult> results; std::vector<RequestResult> results;
@ -37,43 +36,51 @@ struct SerialFrameBatch
std::mutex conditionMutex; std::mutex conditionMutex;
std::condition_variable condition; std::condition_variable condition;
SerialFrameBatch(RequestHandler &requestHandler, json &variables, bool haltOnFailure) : SerialFrameBatch(RequestHandler &requestHandler, json &variables,
requestHandler(requestHandler), bool haltOnFailure)
variables(variables), : requestHandler(requestHandler),
haltOnFailure(haltOnFailure), variables(variables),
frameCount(0), haltOnFailure(haltOnFailure),
sleepUntilFrame(0) frameCount(0),
{} sleepUntilFrame(0)
{
}
}; };
struct ParallelBatchResults struct ParallelBatchResults {
{
RequestHandler &requestHandler; RequestHandler &requestHandler;
std::vector<RequestResult> results; std::vector<RequestResult> results;
std::mutex conditionMutex; std::mutex conditionMutex;
std::condition_variable condition; std::condition_variable condition;
ParallelBatchResults(RequestHandler &requestHandler) : ParallelBatchResults(RequestHandler &requestHandler)
requestHandler(requestHandler) : requestHandler(requestHandler)
{} {
}
}; };
// `{"inputName": "inputNameVariable"}` is essentially `inputName = inputNameVariable` // `{"inputName": "inputNameVariable"}` is essentially `inputName = inputNameVariable`
static void PreProcessVariables(const json &variables, RequestBatchRequest &request) static void PreProcessVariables(const json &variables,
RequestBatchRequest &request)
{ {
if (variables.empty() || !request.InputVariables.is_object() || request.InputVariables.empty() || !request.RequestData.is_object()) if (variables.empty() || !request.InputVariables.is_object() ||
request.InputVariables.empty() || !request.RequestData.is_object())
return; return;
for (auto& [key, value] : request.InputVariables.items()) { for (auto &[key, value] : request.InputVariables.items()) {
if (!value.is_string()) { if (!value.is_string()) {
blog_debug("[WebSocketServer::ProcessRequestBatch] Value of field `%s` in `inputVariables `is not a string. Skipping!", key.c_str()); blog_debug(
"[WebSocketServer::ProcessRequestBatch] Value of field `%s` in `inputVariables `is not a string. Skipping!",
key.c_str());
continue; continue;
} }
std::string valueString = value; std::string valueString = value;
if (!variables.contains(valueString)) { if (!variables.contains(valueString)) {
blog_debug("[WebSocketServer::ProcessRequestBatch] `inputVariables` requested variable `%s`, but it does not exist. Skipping!", valueString.c_str()); blog_debug(
"[WebSocketServer::ProcessRequestBatch] `inputVariables` requested variable `%s`, but it does not exist. Skipping!",
valueString.c_str());
continue; continue;
} }
@ -84,20 +91,28 @@ static void PreProcessVariables(const json &variables, RequestBatchRequest &requ
} }
// `{"sceneItemIdVariable": "sceneItemId"}` is essentially `sceneItemIdVariable = sceneItemId` // `{"sceneItemIdVariable": "sceneItemId"}` is essentially `sceneItemIdVariable = sceneItemId`
static void PostProcessVariables(json &variables, const RequestBatchRequest &request, const RequestResult &requestResult) static void PostProcessVariables(json &variables,
const RequestBatchRequest &request,
const RequestResult &requestResult)
{ {
if (!request.OutputVariables.is_object() || request.OutputVariables.empty() || requestResult.ResponseData.empty()) if (!request.OutputVariables.is_object() ||
request.OutputVariables.empty() ||
requestResult.ResponseData.empty())
return; return;
for (auto& [key, value] : request.OutputVariables.items()) { for (auto &[key, value] : request.OutputVariables.items()) {
if (!value.is_string()) { if (!value.is_string()) {
blog_debug("[WebSocketServer::ProcessRequestBatch] Value of field `%s` in `outputVariables` is not a string. Skipping!", key.c_str()); blog_debug(
"[WebSocketServer::ProcessRequestBatch] Value of field `%s` in `outputVariables` is not a string. Skipping!",
key.c_str());
continue; continue;
} }
std::string valueString = value; std::string valueString = value;
if (!requestResult.ResponseData.contains(valueString)) { if (!requestResult.ResponseData.contains(valueString)) {
blog_debug("[WebSocketServer::ProcessRequestBatch] `outputVariables` requested responseData field `%s`, but it does not exist. Skipping!", valueString.c_str()); blog_debug(
"[WebSocketServer::ProcessRequestBatch] `outputVariables` requested responseData field `%s`, but it does not exist. Skipping!",
valueString.c_str());
continue; continue;
} }
@ -109,13 +124,14 @@ static void ObsTickCallback(void *param, float)
{ {
ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"}; ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"};
auto serialFrameBatch = static_cast<SerialFrameBatch*>(param); auto serialFrameBatch = static_cast<SerialFrameBatch *>(param);
// Increment frame count // Increment frame count
serialFrameBatch->frameCount++; serialFrameBatch->frameCount++;
if (serialFrameBatch->sleepUntilFrame) { if (serialFrameBatch->sleepUntilFrame) {
if (serialFrameBatch->frameCount < serialFrameBatch->sleepUntilFrame) if (serialFrameBatch->frameCount <
serialFrameBatch->sleepUntilFrame)
// Do not process any requests if in "sleep mode" // Do not process any requests if in "sleep mode"
return; return;
else else
@ -126,27 +142,35 @@ static void ObsTickCallback(void *param, float)
// Begin recursing any unprocessed requests // Begin recursing any unprocessed requests
while (!serialFrameBatch->requests.empty()) { while (!serialFrameBatch->requests.empty()) {
// Fetch first in queue // Fetch first in queue
RequestBatchRequest request = serialFrameBatch->requests.front(); RequestBatchRequest request =
serialFrameBatch->requests.front();
// Pre-process batch variables // Pre-process batch variables
PreProcessVariables(serialFrameBatch->variables, request); PreProcessVariables(serialFrameBatch->variables, request);
// Process request and get result // Process request and get result
RequestResult requestResult = serialFrameBatch->requestHandler.ProcessRequest(request); RequestResult requestResult =
serialFrameBatch->requestHandler.ProcessRequest(
request);
// Post-process batch variables // Post-process batch variables
PostProcessVariables(serialFrameBatch->variables, request, requestResult); PostProcessVariables(serialFrameBatch->variables, request,
requestResult);
// Add to results vector // Add to results vector
serialFrameBatch->results.push_back(requestResult); serialFrameBatch->results.push_back(requestResult);
// Remove from front of queue // Remove from front of queue
serialFrameBatch->requests.pop(); serialFrameBatch->requests.pop();
// If haltOnFailure and the request failed, clear the queue to make the batch return early. // If haltOnFailure and the request failed, clear the queue to make the batch return early.
if (serialFrameBatch->haltOnFailure && requestResult.StatusCode != RequestStatus::Success) { if (serialFrameBatch->haltOnFailure &&
serialFrameBatch->requests = std::queue<RequestBatchRequest>(); requestResult.StatusCode != RequestStatus::Success) {
serialFrameBatch->requests =
std::queue<RequestBatchRequest>();
break; break;
} }
// If the processed request tells us to sleep, do so accordingly // If the processed request tells us to sleep, do so accordingly
if (requestResult.SleepFrames) { if (requestResult.SleepFrames) {
serialFrameBatch->sleepUntilFrame = serialFrameBatch->frameCount + requestResult.SleepFrames; serialFrameBatch->sleepUntilFrame =
serialFrameBatch->frameCount +
requestResult.SleepFrames;
break; break;
} }
} }
@ -156,7 +180,11 @@ static void ObsTickCallback(void *param, float)
serialFrameBatch->condition.notify_one(); serialFrameBatch->condition.notify_one();
} }
std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool &threadPool, SessionPtr session, RequestBatchExecutionType::RequestBatchExecutionType executionType, std::vector<RequestBatchRequest> &requests, json &variables, bool haltOnFailure) std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(
QThreadPool &threadPool, SessionPtr session,
RequestBatchExecutionType::RequestBatchExecutionType executionType,
std::vector<RequestBatchRequest> &requests, json &variables,
bool haltOnFailure)
{ {
RequestHandler requestHandler(session); RequestHandler requestHandler(session);
if (executionType == RequestBatchExecutionType::SerialRealtime) { if (executionType == RequestBatchExecutionType::SerialRealtime) {
@ -166,19 +194,22 @@ std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool
for (auto &request : requests) { for (auto &request : requests) {
PreProcessVariables(variables, request); PreProcessVariables(variables, request);
RequestResult requestResult = requestHandler.ProcessRequest(request); RequestResult requestResult =
requestHandler.ProcessRequest(request);
PostProcessVariables(variables, request, requestResult); PostProcessVariables(variables, request, requestResult);
ret.push_back(requestResult); ret.push_back(requestResult);
if (haltOnFailure && requestResult.StatusCode != RequestStatus::Success) if (haltOnFailure &&
requestResult.StatusCode != RequestStatus::Success)
break; break;
} }
return ret; return ret;
} else if (executionType == RequestBatchExecutionType::SerialFrame) { } else if (executionType == RequestBatchExecutionType::SerialFrame) {
SerialFrameBatch serialFrameBatch(requestHandler, variables, haltOnFailure); SerialFrameBatch serialFrameBatch(requestHandler, variables,
haltOnFailure);
// Create Request objects in the worker thread (avoid unnecessary processing in graphics thread) // Create Request objects in the worker thread (avoid unnecessary processing in graphics thread)
for (auto &request : requests) for (auto &request : requests)
@ -188,8 +219,11 @@ std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool
obs_add_tick_callback(ObsTickCallback, &serialFrameBatch); obs_add_tick_callback(ObsTickCallback, &serialFrameBatch);
// Wait until the graphics thread processes the last request in the queue // Wait until the graphics thread processes the last request in the queue
std::unique_lock<std::mutex> lock(serialFrameBatch.conditionMutex); std::unique_lock<std::mutex> lock(
serialFrameBatch.condition.wait(lock, [&serialFrameBatch]{return serialFrameBatch.requests.empty();}); serialFrameBatch.conditionMutex);
serialFrameBatch.condition.wait(lock, [&serialFrameBatch] {
return serialFrameBatch.requests.empty();
});
// Remove the created callback entry since we don't need it anymore // Remove the created callback entry since we don't need it anymore
obs_remove_tick_callback(ObsTickCallback, &serialFrameBatch); obs_remove_tick_callback(ObsTickCallback, &serialFrameBatch);
@ -199,23 +233,33 @@ std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool
ParallelBatchResults parallelResults(requestHandler); ParallelBatchResults parallelResults(requestHandler);
// Acquire the lock early to prevent the batch from finishing before we're ready // Acquire the lock early to prevent the batch from finishing before we're ready
std::unique_lock<std::mutex> lock(parallelResults.conditionMutex); std::unique_lock<std::mutex> lock(
parallelResults.conditionMutex);
// Submit each request as a task to the thread pool to be processed ASAP // Submit each request as a task to the thread pool to be processed ASAP
for (auto &request : requests) { for (auto &request : requests) {
threadPool.start(Utils::Compat::CreateFunctionRunnable([&parallelResults, &request]() { threadPool.start(Utils::Compat::CreateFunctionRunnable(
RequestResult requestResult = parallelResults.requestHandler.ProcessRequest(request); [&parallelResults, &request]() {
RequestResult requestResult =
parallelResults.requestHandler
.ProcessRequest(
request);
std::unique_lock<std::mutex> lock(parallelResults.conditionMutex); std::unique_lock<std::mutex> lock(
parallelResults.results.push_back(requestResult); parallelResults.conditionMutex);
lock.unlock(); parallelResults.results.push_back(
parallelResults.condition.notify_one(); requestResult);
})); lock.unlock();
parallelResults.condition.notify_one();
}));
} }
// Wait for the last request to finish processing // Wait for the last request to finish processing
size_t requestCount = requests.size(); size_t requestCount = requests.size();
parallelResults.condition.wait(lock, [&parallelResults, requestCount]{return parallelResults.results.size() == requestCount;}); parallelResults.condition.wait(lock, [&parallelResults,
requestCount] {
return parallelResults.results.size() == requestCount;
});
return parallelResults.results; return parallelResults.results;
} }

View File

@ -24,5 +24,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "rpc/RequestBatchRequest.h" #include "rpc/RequestBatchRequest.h"
namespace RequestBatchHandler { namespace RequestBatchHandler {
std::vector<RequestResult> ProcessRequestBatch(QThreadPool &threadPool, SessionPtr session, RequestBatchExecutionType::RequestBatchExecutionType executionType, std::vector<RequestBatchRequest> &requests, json &variables, bool haltOnFailure); std::vector<RequestResult> ProcessRequestBatch(
QThreadPool &threadPool, SessionPtr session,
RequestBatchExecutionType::RequestBatchExecutionType executionType,
std::vector<RequestBatchRequest> &requests, json &variables,
bool haltOnFailure);
} }

View File

@ -23,8 +23,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestHandler.h" #include "RequestHandler.h"
const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap{
{
// General // General
{"GetVersion", &RequestHandler::GetVersion}, {"GetVersion", &RequestHandler::GetVersion},
{"GetStats", &RequestHandler::GetStats}, {"GetStats", &RequestHandler::GetStats},
@ -32,14 +31,16 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"CallVendorRequest", &RequestHandler::CallVendorRequest}, {"CallVendorRequest", &RequestHandler::CallVendorRequest},
{"GetHotkeyList", &RequestHandler::GetHotkeyList}, {"GetHotkeyList", &RequestHandler::GetHotkeyList},
{"TriggerHotkeyByName", &RequestHandler::TriggerHotkeyByName}, {"TriggerHotkeyByName", &RequestHandler::TriggerHotkeyByName},
{"TriggerHotkeyByKeySequence", &RequestHandler::TriggerHotkeyByKeySequence}, {"TriggerHotkeyByKeySequence",
&RequestHandler::TriggerHotkeyByKeySequence},
{"Sleep", &RequestHandler::Sleep}, {"Sleep", &RequestHandler::Sleep},
// Config // Config
{"GetPersistentData", &RequestHandler::GetPersistentData}, {"GetPersistentData", &RequestHandler::GetPersistentData},
{"SetPersistentData", &RequestHandler::SetPersistentData}, {"SetPersistentData", &RequestHandler::SetPersistentData},
{"GetSceneCollectionList", &RequestHandler::GetSceneCollectionList}, {"GetSceneCollectionList", &RequestHandler::GetSceneCollectionList},
{"SetCurrentSceneCollection", &RequestHandler::SetCurrentSceneCollection}, {"SetCurrentSceneCollection",
&RequestHandler::SetCurrentSceneCollection},
{"CreateSceneCollection", &RequestHandler::CreateSceneCollection}, {"CreateSceneCollection", &RequestHandler::CreateSceneCollection},
{"GetProfileList", &RequestHandler::GetProfileList}, {"GetProfileList", &RequestHandler::GetProfileList},
{"SetCurrentProfile", &RequestHandler::SetCurrentProfile}, {"SetCurrentProfile", &RequestHandler::SetCurrentProfile},
@ -70,8 +71,10 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"CreateScene", &RequestHandler::CreateScene}, {"CreateScene", &RequestHandler::CreateScene},
{"RemoveScene", &RequestHandler::RemoveScene}, {"RemoveScene", &RequestHandler::RemoveScene},
{"SetSceneName", &RequestHandler::SetSceneName}, {"SetSceneName", &RequestHandler::SetSceneName},
{"GetSceneSceneTransitionOverride", &RequestHandler::GetSceneSceneTransitionOverride}, {"GetSceneSceneTransitionOverride",
{"SetSceneSceneTransitionOverride", &RequestHandler::SetSceneSceneTransitionOverride}, &RequestHandler::GetSceneSceneTransitionOverride},
{"SetSceneSceneTransitionOverride",
&RequestHandler::SetSceneSceneTransitionOverride},
// Inputs // Inputs
{"GetInputList", &RequestHandler::GetInputList}, {"GetInputList", &RequestHandler::GetInputList},
@ -96,23 +99,32 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"SetInputAudioMonitorType", &RequestHandler::SetInputAudioMonitorType}, {"SetInputAudioMonitorType", &RequestHandler::SetInputAudioMonitorType},
{"GetInputAudioTracks", &RequestHandler::GetInputAudioTracks}, {"GetInputAudioTracks", &RequestHandler::GetInputAudioTracks},
{"SetInputAudioTracks", &RequestHandler::SetInputAudioTracks}, {"SetInputAudioTracks", &RequestHandler::SetInputAudioTracks},
{"GetInputPropertiesListPropertyItems", &RequestHandler::GetInputPropertiesListPropertyItems}, {"GetInputPropertiesListPropertyItems",
{"PressInputPropertiesButton", &RequestHandler::PressInputPropertiesButton}, &RequestHandler::GetInputPropertiesListPropertyItems},
{"PressInputPropertiesButton",
&RequestHandler::PressInputPropertiesButton},
// Transitions // Transitions
{"GetTransitionKindList", &RequestHandler::GetTransitionKindList}, {"GetTransitionKindList", &RequestHandler::GetTransitionKindList},
{"GetSceneTransitionList", &RequestHandler::GetSceneTransitionList}, {"GetSceneTransitionList", &RequestHandler::GetSceneTransitionList},
{"GetCurrentSceneTransition", &RequestHandler::GetCurrentSceneTransition}, {"GetCurrentSceneTransition",
{"SetCurrentSceneTransition", &RequestHandler::SetCurrentSceneTransition}, &RequestHandler::GetCurrentSceneTransition},
{"SetCurrentSceneTransitionDuration", &RequestHandler::SetCurrentSceneTransitionDuration}, {"SetCurrentSceneTransition",
{"SetCurrentSceneTransitionSettings", &RequestHandler::SetCurrentSceneTransitionSettings}, &RequestHandler::SetCurrentSceneTransition},
{"GetCurrentSceneTransitionCursor", &RequestHandler::GetCurrentSceneTransitionCursor}, {"SetCurrentSceneTransitionDuration",
{"TriggerStudioModeTransition", &RequestHandler::TriggerStudioModeTransition}, &RequestHandler::SetCurrentSceneTransitionDuration},
{"SetCurrentSceneTransitionSettings",
&RequestHandler::SetCurrentSceneTransitionSettings},
{"GetCurrentSceneTransitionCursor",
&RequestHandler::GetCurrentSceneTransitionCursor},
{"TriggerStudioModeTransition",
&RequestHandler::TriggerStudioModeTransition},
{"SetTBarPosition", &RequestHandler::SetTBarPosition}, {"SetTBarPosition", &RequestHandler::SetTBarPosition},
// Filters // Filters
{"GetSourceFilterList", &RequestHandler::GetSourceFilterList}, {"GetSourceFilterList", &RequestHandler::GetSourceFilterList},
{"GetSourceFilterDefaultSettings", &RequestHandler::GetSourceFilterDefaultSettings}, {"GetSourceFilterDefaultSettings",
&RequestHandler::GetSourceFilterDefaultSettings},
{"CreateSourceFilter", &RequestHandler::CreateSourceFilter}, {"CreateSourceFilter", &RequestHandler::CreateSourceFilter},
{"RemoveSourceFilter", &RequestHandler::RemoveSourceFilter}, {"RemoveSourceFilter", &RequestHandler::RemoveSourceFilter},
{"SetSourceFilterName", &RequestHandler::SetSourceFilterName}, {"SetSourceFilterName", &RequestHandler::SetSourceFilterName},
@ -138,8 +150,10 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"SetSceneItemIndex", &RequestHandler::SetSceneItemIndex}, {"SetSceneItemIndex", &RequestHandler::SetSceneItemIndex},
{"GetSceneItemBlendMode", &RequestHandler::GetSceneItemBlendMode}, {"GetSceneItemBlendMode", &RequestHandler::GetSceneItemBlendMode},
{"SetSceneItemBlendMode", &RequestHandler::SetSceneItemBlendMode}, {"SetSceneItemBlendMode", &RequestHandler::SetSceneItemBlendMode},
{"GetSceneItemPrivateSettings", &RequestHandler::GetSceneItemPrivateSettings}, {"GetSceneItemPrivateSettings",
{"SetSceneItemPrivateSettings", &RequestHandler::SetSceneItemPrivateSettings}, &RequestHandler::GetSceneItemPrivateSettings},
{"SetSceneItemPrivateSettings",
&RequestHandler::SetSceneItemPrivateSettings},
// Outputs // Outputs
{"GetVirtualCamStatus", &RequestHandler::GetVirtualCamStatus}, {"GetVirtualCamStatus", &RequestHandler::GetVirtualCamStatus},
@ -151,7 +165,8 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"StartReplayBuffer", &RequestHandler::StartReplayBuffer}, {"StartReplayBuffer", &RequestHandler::StartReplayBuffer},
{"StopReplayBuffer", &RequestHandler::StopReplayBuffer}, {"StopReplayBuffer", &RequestHandler::StopReplayBuffer},
{"SaveReplayBuffer", &RequestHandler::SaveReplayBuffer}, {"SaveReplayBuffer", &RequestHandler::SaveReplayBuffer},
{"GetLastReplayBufferReplay", &RequestHandler::GetLastReplayBufferReplay}, {"GetLastReplayBufferReplay",
&RequestHandler::GetLastReplayBufferReplay},
// Stream // Stream
{"GetStreamStatus", &RequestHandler::GetStreamStatus}, {"GetStreamStatus", &RequestHandler::GetStreamStatus},
@ -178,34 +193,37 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
// Ui // Ui
{"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled}, {"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled},
{"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled}, {"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled},
{"OpenInputPropertiesDialog", &RequestHandler::OpenInputPropertiesDialog}, {"OpenInputPropertiesDialog",
&RequestHandler::OpenInputPropertiesDialog},
{"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog}, {"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog},
{"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog}, {"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog},
{"GetMonitorList", &RequestHandler::GetMonitorList}, {"GetMonitorList", &RequestHandler::GetMonitorList},
}; };
RequestHandler::RequestHandler(SessionPtr session) : RequestHandler::RequestHandler(SessionPtr session) : _session(session) {}
_session(session)
{
}
RequestResult RequestHandler::ProcessRequest(const Request& request) RequestResult RequestHandler::ProcessRequest(const Request &request)
{ {
#ifdef PLUGIN_TESTS #ifdef PLUGIN_TESTS
ScopeProfiler prof{"obs_websocket_request_processing"}; ScopeProfiler prof{"obs_websocket_request_processing"};
#endif #endif
if (!request.RequestData.is_object() && !request.RequestData.is_null()) if (!request.RequestData.is_object() && !request.RequestData.is_null())
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "Your request data is not an object."); return RequestResult::Error(
RequestStatus::InvalidRequestFieldType,
"Your request data is not an object.");
if (request.RequestType.empty()) if (request.RequestType.empty())
return RequestResult::Error(RequestStatus::MissingRequestType, "Your request is missing a `requestType`"); return RequestResult::Error(
RequestStatus::MissingRequestType,
"Your request is missing a `requestType`");
RequestMethodHandler handler; RequestMethodHandler handler;
try { try {
handler = _handlerMap.at(request.RequestType); handler = _handlerMap.at(request.RequestType);
} catch (const std::out_of_range& oor) { } catch (const std::out_of_range &oor) {
return RequestResult::Error(RequestStatus::UnknownRequestType, "Your request type is not valid."); return RequestResult::Error(RequestStatus::UnknownRequestType,
"Your request type is not valid.");
} }
return std::bind(handler, this, std::placeholders::_1)(request); return std::bind(handler, this, std::placeholders::_1)(request);
@ -214,7 +232,7 @@ RequestResult RequestHandler::ProcessRequest(const Request& request)
std::vector<std::string> RequestHandler::GetRequestList() std::vector<std::string> RequestHandler::GetRequestList()
{ {
std::vector<std::string> ret; std::vector<std::string> ret;
for (auto const& [key, val] : _handlerMap) { for (auto const &[key, val] : _handlerMap) {
ret.push_back(key); ret.push_back(key);
} }

View File

@ -33,174 +33,175 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
class RequestHandler; class RequestHandler;
typedef RequestResult(RequestHandler::*RequestMethodHandler)(const Request&); typedef RequestResult (RequestHandler::*RequestMethodHandler)(const Request &);
class RequestHandler { class RequestHandler {
public: public:
RequestHandler(SessionPtr session = nullptr); RequestHandler(SessionPtr session = nullptr);
RequestResult ProcessRequest(const Request& request); RequestResult ProcessRequest(const Request &request);
std::vector<std::string> GetRequestList(); std::vector<std::string> GetRequestList();
private: private:
// General // General
RequestResult GetVersion(const Request&); RequestResult GetVersion(const Request &);
RequestResult GetStats(const Request&); RequestResult GetStats(const Request &);
RequestResult BroadcastCustomEvent(const Request&); RequestResult BroadcastCustomEvent(const Request &);
RequestResult CallVendorRequest(const Request&); RequestResult CallVendorRequest(const Request &);
RequestResult GetHotkeyList(const Request&); RequestResult GetHotkeyList(const Request &);
RequestResult TriggerHotkeyByName(const Request&); RequestResult TriggerHotkeyByName(const Request &);
RequestResult TriggerHotkeyByKeySequence(const Request&); RequestResult TriggerHotkeyByKeySequence(const Request &);
RequestResult Sleep(const Request&); RequestResult Sleep(const Request &);
// Config // Config
RequestResult GetPersistentData(const Request&); RequestResult GetPersistentData(const Request &);
RequestResult SetPersistentData(const Request&); RequestResult SetPersistentData(const Request &);
RequestResult GetSceneCollectionList(const Request&); RequestResult GetSceneCollectionList(const Request &);
RequestResult SetCurrentSceneCollection(const Request&); RequestResult SetCurrentSceneCollection(const Request &);
RequestResult CreateSceneCollection(const Request&); RequestResult CreateSceneCollection(const Request &);
RequestResult GetProfileList(const Request&); RequestResult GetProfileList(const Request &);
RequestResult SetCurrentProfile(const Request&); RequestResult SetCurrentProfile(const Request &);
RequestResult CreateProfile(const Request&); RequestResult CreateProfile(const Request &);
RequestResult RemoveProfile(const Request&); RequestResult RemoveProfile(const Request &);
RequestResult GetProfileParameter(const Request&); RequestResult GetProfileParameter(const Request &);
RequestResult SetProfileParameter(const Request&); RequestResult SetProfileParameter(const Request &);
RequestResult GetVideoSettings(const Request&); RequestResult GetVideoSettings(const Request &);
RequestResult SetVideoSettings(const Request&); RequestResult SetVideoSettings(const Request &);
RequestResult GetStreamServiceSettings(const Request&); RequestResult GetStreamServiceSettings(const Request &);
RequestResult SetStreamServiceSettings(const Request&); RequestResult SetStreamServiceSettings(const Request &);
RequestResult GetRecordDirectory(const Request&); RequestResult GetRecordDirectory(const Request &);
// Sources // Sources
RequestResult GetSourceActive(const Request&); RequestResult GetSourceActive(const Request &);
RequestResult GetSourceScreenshot(const Request&); RequestResult GetSourceScreenshot(const Request &);
RequestResult SaveSourceScreenshot(const Request&); RequestResult SaveSourceScreenshot(const Request &);
RequestResult GetSourcePrivateSettings(const Request&); RequestResult GetSourcePrivateSettings(const Request &);
RequestResult SetSourcePrivateSettings(const Request&); RequestResult SetSourcePrivateSettings(const Request &);
// Scenes // Scenes
RequestResult GetSceneList(const Request&); RequestResult GetSceneList(const Request &);
RequestResult GetGroupList(const Request&); RequestResult GetGroupList(const Request &);
RequestResult GetCurrentProgramScene(const Request&); RequestResult GetCurrentProgramScene(const Request &);
RequestResult SetCurrentProgramScene(const Request&); RequestResult SetCurrentProgramScene(const Request &);
RequestResult GetCurrentPreviewScene(const Request&); RequestResult GetCurrentPreviewScene(const Request &);
RequestResult SetCurrentPreviewScene(const Request&); RequestResult SetCurrentPreviewScene(const Request &);
RequestResult CreateScene(const Request&); RequestResult CreateScene(const Request &);
RequestResult RemoveScene(const Request&); RequestResult RemoveScene(const Request &);
RequestResult SetSceneName(const Request&); RequestResult SetSceneName(const Request &);
RequestResult GetSceneSceneTransitionOverride(const Request&); RequestResult GetSceneSceneTransitionOverride(const Request &);
RequestResult SetSceneSceneTransitionOverride(const Request&); RequestResult SetSceneSceneTransitionOverride(const Request &);
// Inputs // Inputs
RequestResult GetInputList(const Request&); RequestResult GetInputList(const Request &);
RequestResult GetInputKindList(const Request&); RequestResult GetInputKindList(const Request &);
RequestResult GetSpecialInputs(const Request&); RequestResult GetSpecialInputs(const Request &);
RequestResult CreateInput(const Request&); RequestResult CreateInput(const Request &);
RequestResult RemoveInput(const Request&); RequestResult RemoveInput(const Request &);
RequestResult SetInputName(const Request&); RequestResult SetInputName(const Request &);
RequestResult GetInputDefaultSettings(const Request&); RequestResult GetInputDefaultSettings(const Request &);
RequestResult GetInputSettings(const Request&); RequestResult GetInputSettings(const Request &);
RequestResult SetInputSettings(const Request&); RequestResult SetInputSettings(const Request &);
RequestResult GetInputMute(const Request&); RequestResult GetInputMute(const Request &);
RequestResult SetInputMute(const Request&); RequestResult SetInputMute(const Request &);
RequestResult ToggleInputMute(const Request&); RequestResult ToggleInputMute(const Request &);
RequestResult GetInputVolume(const Request&); RequestResult GetInputVolume(const Request &);
RequestResult SetInputVolume(const Request&); RequestResult SetInputVolume(const Request &);
RequestResult GetInputAudioBalance(const Request&); RequestResult GetInputAudioBalance(const Request &);
RequestResult SetInputAudioBalance(const Request&); RequestResult SetInputAudioBalance(const Request &);
RequestResult GetInputAudioSyncOffset(const Request&); RequestResult GetInputAudioSyncOffset(const Request &);
RequestResult SetInputAudioSyncOffset(const Request&); RequestResult SetInputAudioSyncOffset(const Request &);
RequestResult GetInputAudioMonitorType(const Request&); RequestResult GetInputAudioMonitorType(const Request &);
RequestResult SetInputAudioMonitorType(const Request&); RequestResult SetInputAudioMonitorType(const Request &);
RequestResult GetInputAudioTracks(const Request&); RequestResult GetInputAudioTracks(const Request &);
RequestResult SetInputAudioTracks(const Request&); RequestResult SetInputAudioTracks(const Request &);
RequestResult GetInputPropertiesListPropertyItems(const Request&); RequestResult GetInputPropertiesListPropertyItems(const Request &);
RequestResult PressInputPropertiesButton(const Request&); RequestResult PressInputPropertiesButton(const Request &);
// Transitions // Transitions
RequestResult GetTransitionKindList(const Request&); RequestResult GetTransitionKindList(const Request &);
RequestResult GetSceneTransitionList(const Request&); RequestResult GetSceneTransitionList(const Request &);
RequestResult GetCurrentSceneTransition(const Request&); RequestResult GetCurrentSceneTransition(const Request &);
RequestResult SetCurrentSceneTransition(const Request&); RequestResult SetCurrentSceneTransition(const Request &);
RequestResult SetCurrentSceneTransitionDuration(const Request&); RequestResult SetCurrentSceneTransitionDuration(const Request &);
RequestResult SetCurrentSceneTransitionSettings(const Request&); RequestResult SetCurrentSceneTransitionSettings(const Request &);
RequestResult GetCurrentSceneTransitionCursor(const Request&); RequestResult GetCurrentSceneTransitionCursor(const Request &);
RequestResult TriggerStudioModeTransition(const Request&); RequestResult TriggerStudioModeTransition(const Request &);
RequestResult SetTBarPosition(const Request&); RequestResult SetTBarPosition(const Request &);
// Filters // Filters
RequestResult GetSourceFilterList(const Request&); RequestResult GetSourceFilterList(const Request &);
RequestResult GetSourceFilterDefaultSettings(const Request&); RequestResult GetSourceFilterDefaultSettings(const Request &);
RequestResult CreateSourceFilter(const Request&); RequestResult CreateSourceFilter(const Request &);
RequestResult RemoveSourceFilter(const Request&); RequestResult RemoveSourceFilter(const Request &);
RequestResult SetSourceFilterName(const Request&); RequestResult SetSourceFilterName(const Request &);
RequestResult GetSourceFilter(const Request&); RequestResult GetSourceFilter(const Request &);
RequestResult SetSourceFilterIndex(const Request&); RequestResult SetSourceFilterIndex(const Request &);
RequestResult SetSourceFilterSettings(const Request&); RequestResult SetSourceFilterSettings(const Request &);
RequestResult SetSourceFilterEnabled(const Request&); RequestResult SetSourceFilterEnabled(const Request &);
// Scene Items // Scene Items
RequestResult GetSceneItemList(const Request&); RequestResult GetSceneItemList(const Request &);
RequestResult GetGroupSceneItemList(const Request&); RequestResult GetGroupSceneItemList(const Request &);
RequestResult GetSceneItemId(const Request&); RequestResult GetSceneItemId(const Request &);
RequestResult CreateSceneItem(const Request&); RequestResult CreateSceneItem(const Request &);
RequestResult RemoveSceneItem(const Request&); RequestResult RemoveSceneItem(const Request &);
RequestResult DuplicateSceneItem(const Request&); RequestResult DuplicateSceneItem(const Request &);
RequestResult GetSceneItemTransform(const Request&); RequestResult GetSceneItemTransform(const Request &);
RequestResult SetSceneItemTransform(const Request&); RequestResult SetSceneItemTransform(const Request &);
RequestResult GetSceneItemEnabled(const Request&); RequestResult GetSceneItemEnabled(const Request &);
RequestResult SetSceneItemEnabled(const Request&); RequestResult SetSceneItemEnabled(const Request &);
RequestResult GetSceneItemLocked(const Request&); RequestResult GetSceneItemLocked(const Request &);
RequestResult SetSceneItemLocked(const Request&); RequestResult SetSceneItemLocked(const Request &);
RequestResult GetSceneItemIndex(const Request&); RequestResult GetSceneItemIndex(const Request &);
RequestResult SetSceneItemIndex(const Request&); RequestResult SetSceneItemIndex(const Request &);
RequestResult GetSceneItemBlendMode(const Request&); RequestResult GetSceneItemBlendMode(const Request &);
RequestResult SetSceneItemBlendMode(const Request&); RequestResult SetSceneItemBlendMode(const Request &);
RequestResult GetSceneItemPrivateSettings(const Request&); RequestResult GetSceneItemPrivateSettings(const Request &);
RequestResult SetSceneItemPrivateSettings(const Request&); RequestResult SetSceneItemPrivateSettings(const Request &);
// Outputs // Outputs
RequestResult GetVirtualCamStatus(const Request&); RequestResult GetVirtualCamStatus(const Request &);
RequestResult ToggleVirtualCam(const Request&); RequestResult ToggleVirtualCam(const Request &);
RequestResult StartVirtualCam(const Request&); RequestResult StartVirtualCam(const Request &);
RequestResult StopVirtualCam(const Request&); RequestResult StopVirtualCam(const Request &);
RequestResult GetReplayBufferStatus(const Request&); RequestResult GetReplayBufferStatus(const Request &);
RequestResult ToggleReplayBuffer(const Request&); RequestResult ToggleReplayBuffer(const Request &);
RequestResult StartReplayBuffer(const Request&); RequestResult StartReplayBuffer(const Request &);
RequestResult StopReplayBuffer(const Request&); RequestResult StopReplayBuffer(const Request &);
RequestResult SaveReplayBuffer(const Request&); RequestResult SaveReplayBuffer(const Request &);
RequestResult GetLastReplayBufferReplay(const Request&); RequestResult GetLastReplayBufferReplay(const Request &);
// Stream // Stream
RequestResult GetStreamStatus(const Request&); RequestResult GetStreamStatus(const Request &);
RequestResult ToggleStream(const Request&); RequestResult ToggleStream(const Request &);
RequestResult StartStream(const Request&); RequestResult StartStream(const Request &);
RequestResult StopStream(const Request&); RequestResult StopStream(const Request &);
RequestResult SendStreamCaption(const Request&); RequestResult SendStreamCaption(const Request &);
// Record // Record
RequestResult GetRecordStatus(const Request&); RequestResult GetRecordStatus(const Request &);
RequestResult ToggleRecord(const Request&); RequestResult ToggleRecord(const Request &);
RequestResult StartRecord(const Request&); RequestResult StartRecord(const Request &);
RequestResult StopRecord(const Request&); RequestResult StopRecord(const Request &);
RequestResult ToggleRecordPause(const Request&); RequestResult ToggleRecordPause(const Request &);
RequestResult PauseRecord(const Request&); RequestResult PauseRecord(const Request &);
RequestResult ResumeRecord(const Request&); RequestResult ResumeRecord(const Request &);
// Media Inputs // Media Inputs
RequestResult GetMediaInputStatus(const Request&); RequestResult GetMediaInputStatus(const Request &);
RequestResult SetMediaInputCursor(const Request&); RequestResult SetMediaInputCursor(const Request &);
RequestResult OffsetMediaInputCursor(const Request&); RequestResult OffsetMediaInputCursor(const Request &);
RequestResult TriggerMediaInputAction(const Request&); RequestResult TriggerMediaInputAction(const Request &);
// Ui // Ui
RequestResult GetStudioModeEnabled(const Request&); RequestResult GetStudioModeEnabled(const Request &);
RequestResult SetStudioModeEnabled(const Request&); RequestResult SetStudioModeEnabled(const Request &);
RequestResult OpenInputPropertiesDialog(const Request&); RequestResult OpenInputPropertiesDialog(const Request &);
RequestResult OpenInputFiltersDialog(const Request&); RequestResult OpenInputFiltersDialog(const Request &);
RequestResult OpenInputInteractDialog(const Request&); RequestResult OpenInputInteractDialog(const Request &);
RequestResult GetMonitorList(const Request&); RequestResult GetMonitorList(const Request &);
SessionPtr _session; SessionPtr _session;
static const std::unordered_map<std::string, RequestMethodHandler> _handlerMap; static const std::unordered_map<std::string, RequestMethodHandler>
_handlerMap;
}; };

View File

@ -37,27 +37,34 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetPersistentData(const Request& request) RequestResult RequestHandler::GetPersistentData(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!(request.ValidateString("realm", statusCode, comment) && request.ValidateString("slotName", statusCode, comment))) if (!(request.ValidateString("realm", statusCode, comment) &&
request.ValidateString("slotName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string realm = request.RequestData["realm"]; std::string realm = request.RequestData["realm"];
std::string slotName = request.RequestData["slotName"]; std::string slotName = request.RequestData["slotName"];
std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath(); std::string persistentDataPath =
Utils::Obs::StringHelper::GetCurrentProfilePath();
if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL") if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL")
persistentDataPath += "/../../../obsWebSocketPersistentData.json"; persistentDataPath +=
"/../../../obsWebSocketPersistentData.json";
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE") else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json"; persistentDataPath += "/obsWebSocketPersistentData.json";
else else
return RequestResult::Error(RequestStatus::ResourceNotFound, "You have specified an invalid persistent data realm."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"You have specified an invalid persistent data realm.");
json responseData; json responseData;
json persistentData; json persistentData;
if (Utils::Json::GetJsonFileContent(persistentDataPath, persistentData) && persistentData.contains(slotName)) if (Utils::Json::GetJsonFileContent(persistentDataPath,
persistentData) &&
persistentData.contains(slotName))
responseData["slotValue"] = persistentData[slotName]; responseData["slotValue"] = persistentData[slotName];
else else
responseData["slotValue"] = nullptr; responseData["slotValue"] = nullptr;
@ -79,30 +86,39 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetPersistentData(const Request& request) RequestResult RequestHandler::SetPersistentData(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!(request.ValidateString("realm", statusCode, comment) && request.ValidateString("slotName", statusCode, comment) && request.ValidateBasic("slotValue", statusCode, comment))) if (!(request.ValidateString("realm", statusCode, comment) &&
request.ValidateString("slotName", statusCode, comment) &&
request.ValidateBasic("slotValue", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string realm = request.RequestData["realm"]; std::string realm = request.RequestData["realm"];
std::string slotName = request.RequestData["slotName"]; std::string slotName = request.RequestData["slotName"];
json slotValue = request.RequestData["slotValue"]; json slotValue = request.RequestData["slotValue"];
std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath(); std::string persistentDataPath =
Utils::Obs::StringHelper::GetCurrentProfilePath();
if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL") if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL")
persistentDataPath += "/../../../obsWebSocketPersistentData.json"; persistentDataPath +=
"/../../../obsWebSocketPersistentData.json";
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE") else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json"; persistentDataPath += "/obsWebSocketPersistentData.json";
else else
return RequestResult::Error(RequestStatus::ResourceNotFound, "You have specified an invalid persistent data realm."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"You have specified an invalid persistent data realm.");
json persistentData = json::object(); json persistentData = json::object();
Utils::Json::GetJsonFileContent(persistentDataPath, persistentData); Utils::Json::GetJsonFileContent(persistentDataPath, persistentData);
persistentData[slotName] = slotValue; persistentData[slotName] = slotValue;
if (!Utils::Json::SetJsonFileContent(persistentDataPath, persistentData)) if (!Utils::Json::SetJsonFileContent(persistentDataPath,
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Unable to write persistent data. No permissions?"); persistentData))
return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Unable to write persistent data. No permissions?");
return RequestResult::Success(); return RequestResult::Success();
} }
@ -120,11 +136,13 @@ RequestResult RequestHandler::SetPersistentData(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetSceneCollectionList(const Request&) RequestResult RequestHandler::GetSceneCollectionList(const Request &)
{ {
json responseData; json responseData;
responseData["currentSceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection(); responseData["currentSceneCollectionName"] =
responseData["sceneCollections"] = Utils::Obs::ArrayHelper::GetSceneCollectionList(); Utils::Obs::StringHelper::GetCurrentSceneCollection();
responseData["sceneCollections"] =
Utils::Obs::ArrayHelper::GetSceneCollectionList();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -142,25 +160,33 @@ RequestResult RequestHandler::GetSceneCollectionList(const Request&)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request) RequestResult RequestHandler::SetCurrentSceneCollection(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!request.ValidateString("sceneCollectionName", statusCode, comment)) if (!request.ValidateString("sceneCollectionName", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string sceneCollectionName = request.RequestData["sceneCollectionName"]; std::string sceneCollectionName =
request.RequestData["sceneCollectionName"];
auto sceneCollections = Utils::Obs::ArrayHelper::GetSceneCollectionList(); auto sceneCollections =
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) == sceneCollections.end()) Utils::Obs::ArrayHelper::GetSceneCollectionList();
if (std::find(sceneCollections.begin(), sceneCollections.end(),
sceneCollectionName) == sceneCollections.end())
return RequestResult::Error(RequestStatus::ResourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
std::string currentSceneCollectionName = Utils::Obs::StringHelper::GetCurrentSceneCollection(); std::string currentSceneCollectionName =
Utils::Obs::StringHelper::GetCurrentSceneCollection();
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
if (currentSceneCollectionName != sceneCollectionName) { if (currentSceneCollectionName != sceneCollectionName) {
obs_queue_task(OBS_TASK_UI, [](void* param) { obs_queue_task(
obs_frontend_set_current_scene_collection(static_cast<const char*>(param)); OBS_TASK_UI,
}, (void*)sceneCollectionName.c_str(), true); [](void *param) {
obs_frontend_set_current_scene_collection(
static_cast<const char *>(param));
},
(void *)sceneCollectionName.c_str(), true);
} }
return RequestResult::Success(); return RequestResult::Success();
@ -180,24 +206,34 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::CreateSceneCollection(const Request& request) RequestResult RequestHandler::CreateSceneCollection(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!request.ValidateString("sceneCollectionName", statusCode, comment)) if (!request.ValidateString("sceneCollectionName", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string sceneCollectionName = request.RequestData["sceneCollectionName"]; std::string sceneCollectionName =
request.RequestData["sceneCollectionName"];
auto sceneCollections = Utils::Obs::ArrayHelper::GetSceneCollectionList(); auto sceneCollections =
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end()) Utils::Obs::ArrayHelper::GetSceneCollectionList();
return RequestResult::Error(RequestStatus::ResourceAlreadyExists); if (std::find(sceneCollections.begin(), sceneCollections.end(),
sceneCollectionName) != sceneCollections.end())
return RequestResult::Error(
RequestStatus::ResourceAlreadyExists);
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window()); QMainWindow *mainWindow =
static_cast<QMainWindow *>(obs_frontend_get_main_window());
bool success = false; bool success = false;
QMetaObject::invokeMethod(mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(bool, true), Q_ARG(QString, QString::fromStdString(sceneCollectionName))); QMetaObject::invokeMethod(
mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(bool, success), Q_ARG(bool, true),
Q_ARG(QString, QString::fromStdString(sceneCollectionName)));
if (!success) if (!success)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene collection."); return RequestResult::Error(
RequestStatus::ResourceCreationFailed,
"Failed to create the scene collection.");
return RequestResult::Success(); return RequestResult::Success();
} }
@ -215,10 +251,11 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetProfileList(const Request&) RequestResult RequestHandler::GetProfileList(const Request &)
{ {
json responseData; json responseData;
responseData["currentProfileName"] = Utils::Obs::StringHelper::GetCurrentProfile(); responseData["currentProfileName"] =
Utils::Obs::StringHelper::GetCurrentProfile();
responseData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList(); responseData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -235,7 +272,7 @@ RequestResult RequestHandler::GetProfileList(const Request&)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetCurrentProfile(const Request& request) RequestResult RequestHandler::SetCurrentProfile(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -245,15 +282,21 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
std::string profileName = request.RequestData["profileName"]; std::string profileName = request.RequestData["profileName"];
auto profiles = Utils::Obs::ArrayHelper::GetProfileList(); auto profiles = Utils::Obs::ArrayHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end()) if (std::find(profiles.begin(), profiles.end(), profileName) ==
profiles.end())
return RequestResult::Error(RequestStatus::ResourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
std::string currentProfileName = Utils::Obs::StringHelper::GetCurrentProfile(); std::string currentProfileName =
Utils::Obs::StringHelper::GetCurrentProfile();
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
if (currentProfileName != profileName) { if (currentProfileName != profileName) {
obs_queue_task(OBS_TASK_UI, [](void* param) { obs_queue_task(
obs_frontend_set_current_profile(static_cast<const char*>(param)); OBS_TASK_UI,
}, (void*)profileName.c_str(), true); [](void *param) {
obs_frontend_set_current_profile(
static_cast<const char *>(param));
},
(void *)profileName.c_str(), true);
} }
return RequestResult::Success(); return RequestResult::Success();
@ -271,7 +314,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::CreateProfile(const Request& request) RequestResult RequestHandler::CreateProfile(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -281,11 +324,16 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
std::string profileName = request.RequestData["profileName"]; std::string profileName = request.RequestData["profileName"];
auto profiles = Utils::Obs::ArrayHelper::GetProfileList(); auto profiles = Utils::Obs::ArrayHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end()) if (std::find(profiles.begin(), profiles.end(), profileName) !=
return RequestResult::Error(RequestStatus::ResourceAlreadyExists); profiles.end())
return RequestResult::Error(
RequestStatus::ResourceAlreadyExists);
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window()); QMainWindow *mainWindow =
QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName))); static_cast<QMainWindow *>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(
mainWindow, "NewProfile", Qt::BlockingQueuedConnection,
Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success(); return RequestResult::Success();
} }
@ -302,7 +350,7 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::RemoveProfile(const Request& request) RequestResult RequestHandler::RemoveProfile(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -312,14 +360,18 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
std::string profileName = request.RequestData["profileName"]; std::string profileName = request.RequestData["profileName"];
auto profiles = Utils::Obs::ArrayHelper::GetProfileList(); auto profiles = Utils::Obs::ArrayHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end()) if (std::find(profiles.begin(), profiles.end(), profileName) ==
profiles.end())
return RequestResult::Error(RequestStatus::ResourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
if (profiles.size() < 2) if (profiles.size() < 2)
return RequestResult::Error(RequestStatus::NotEnoughResources); return RequestResult::Error(RequestStatus::NotEnoughResources);
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window()); QMainWindow *mainWindow =
QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName))); static_cast<QMainWindow *>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(
mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection,
Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success(); return RequestResult::Success();
} }
@ -340,27 +392,40 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetProfileParameter(const Request& request) RequestResult RequestHandler::GetProfileParameter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!(request.ValidateString("parameterCategory", statusCode, comment) && request.ValidateString("parameterName", statusCode, comment))) if (!(request.ValidateString("parameterCategory", statusCode,
comment) &&
request.ValidateString("parameterName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string parameterCategory = request.RequestData["parameterCategory"]; std::string parameterCategory =
request.RequestData["parameterCategory"];
std::string parameterName = request.RequestData["parameterName"]; std::string parameterName = request.RequestData["parameterName"];
config_t* profile = obs_frontend_get_profile_config(); config_t *profile = obs_frontend_get_profile_config();
if (!profile) if (!profile)
blog(LOG_ERROR, "[RequestHandler::GetProfileParameter] Profile is invalid."); blog(LOG_ERROR,
"[RequestHandler::GetProfileParameter] Profile is invalid.");
json responseData; json responseData;
if (config_has_default_value(profile, parameterCategory.c_str(), parameterName.c_str())) { if (config_has_default_value(profile, parameterCategory.c_str(),
responseData["parameterValue"] = config_get_string(profile, parameterCategory.c_str(), parameterName.c_str()); parameterName.c_str())) {
responseData["defaultParameterValue"] = config_get_default_string(profile, parameterCategory.c_str(), parameterName.c_str()); responseData["parameterValue"] =
} else if (config_has_user_value(profile, parameterCategory.c_str(), parameterName.c_str())) { config_get_string(profile, parameterCategory.c_str(),
responseData["parameterValue"] = config_get_string(profile, parameterCategory.c_str(), parameterName.c_str()); parameterName.c_str());
responseData["defaultParameterValue"] =
config_get_default_string(profile,
parameterCategory.c_str(),
parameterName.c_str());
} else if (config_has_user_value(profile, parameterCategory.c_str(),
parameterName.c_str())) {
responseData["parameterValue"] =
config_get_string(profile, parameterCategory.c_str(),
parameterName.c_str());
responseData["defaultParameterValue"] = nullptr; responseData["defaultParameterValue"] = nullptr;
} else { } else {
responseData["parameterValue"] = nullptr; responseData["parameterValue"] = nullptr;
@ -384,28 +449,39 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetProfileParameter(const Request& request) RequestResult RequestHandler::SetProfileParameter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!(request.ValidateString("parameterCategory", statusCode, comment) && if (!(request.ValidateString("parameterCategory", statusCode,
request.ValidateString("parameterName", statusCode, comment))) comment) &&
request.ValidateString("parameterName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string parameterCategory = request.RequestData["parameterCategory"]; std::string parameterCategory =
request.RequestData["parameterCategory"];
std::string parameterName = request.RequestData["parameterName"]; std::string parameterName = request.RequestData["parameterName"];
config_t* profile = obs_frontend_get_profile_config(); config_t *profile = obs_frontend_get_profile_config();
// Using check helpers here would just make the logic more complicated // Using check helpers here would just make the logic more complicated
if (!request.RequestData.contains("parameterValue") || request.RequestData["parameterValue"].is_null()) { if (!request.RequestData.contains("parameterValue") ||
if (!config_remove_value(profile, parameterCategory.c_str(), parameterName.c_str())) request.RequestData["parameterValue"].is_null()) {
return RequestResult::Error(RequestStatus::ResourceNotFound, "There are no existing instances of that profile parameter."); if (!config_remove_value(profile, parameterCategory.c_str(),
parameterName.c_str()))
return RequestResult::Error(
RequestStatus::ResourceNotFound,
"There are no existing instances of that profile parameter.");
} else if (request.RequestData["parameterValue"].is_string()) { } else if (request.RequestData["parameterValue"].is_string()) {
std::string parameterValue = request.RequestData["parameterValue"]; std::string parameterValue =
config_set_string(profile, parameterCategory.c_str(), parameterName.c_str(), parameterValue.c_str()); request.RequestData["parameterValue"];
config_set_string(profile, parameterCategory.c_str(),
parameterName.c_str(),
parameterValue.c_str());
} else { } else {
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The field `parameterValue` must be a string."); return RequestResult::Error(
RequestStatus::InvalidRequestFieldType,
"The field `parameterValue` must be a string.");
} }
config_save(profile); config_save(profile);
@ -432,11 +508,13 @@ RequestResult RequestHandler::SetProfileParameter(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetVideoSettings(const Request&) RequestResult RequestHandler::GetVideoSettings(const Request &)
{ {
struct obs_video_info ovi; struct obs_video_info ovi;
if (!obs_get_video_info(&ovi)) if (!obs_get_video_info(&ovi))
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Unable to get internal OBS video info."); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Unable to get internal OBS video info.");
json responseData; json responseData;
responseData["fpsNumerator"] = ovi.fps_num; responseData["fpsNumerator"] = ovi.fps_num;
@ -468,41 +546,63 @@ RequestResult RequestHandler::GetVideoSettings(const Request&)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetVideoSettings(const Request& request) RequestResult RequestHandler::SetVideoSettings(const Request &request)
{ {
if (obs_video_active()) if (obs_video_active())
return RequestResult::Error(RequestStatus::OutputRunning, "Video settings cannot be changed while an output is active."); return RequestResult::Error(
RequestStatus::OutputRunning,
"Video settings cannot be changed while an output is active.");
RequestStatus::RequestStatus statusCode = RequestStatus::NoError; RequestStatus::RequestStatus statusCode = RequestStatus::NoError;
std::string comment; std::string comment;
bool changeFps = (request.Contains("fpsNumerator") && request.Contains("fpsDenominator")); bool changeFps = (request.Contains("fpsNumerator") &&
if (changeFps && !(request.ValidateOptionalNumber("fpsNumerator", statusCode, comment, 1) && request.ValidateOptionalNumber("fpsDenominator", statusCode, comment, 1))) request.Contains("fpsDenominator"));
if (changeFps && !(request.ValidateOptionalNumber(
"fpsNumerator", statusCode, comment, 1) &&
request.ValidateOptionalNumber(
"fpsDenominator", statusCode, comment, 1)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
bool changeBaseRes = (request.Contains("baseWidth") && request.Contains("baseHeight")); bool changeBaseRes = (request.Contains("baseWidth") &&
if (changeBaseRes && !(request.ValidateOptionalNumber("baseWidth", statusCode, comment, 8, 4096) && request.ValidateOptionalNumber("baseHeight", statusCode, comment, 8, 4096))) request.Contains("baseHeight"));
if (changeBaseRes &&
!(request.ValidateOptionalNumber("baseWidth", statusCode, comment,
8, 4096) &&
request.ValidateOptionalNumber("baseHeight", statusCode, comment,
8, 4096)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
bool changeOutputRes = (request.Contains("outputWidth") && request.Contains("outputHeight")); bool changeOutputRes = (request.Contains("outputWidth") &&
if (changeOutputRes && !(request.ValidateOptionalNumber("outputWidth", statusCode, comment, 8, 4096) && request.ValidateOptionalNumber("outputHeight", statusCode, comment, 8, 4096))) request.Contains("outputHeight"));
if (changeOutputRes &&
!(request.ValidateOptionalNumber("outputWidth", statusCode, comment,
8, 4096) &&
request.ValidateOptionalNumber("outputHeight", statusCode,
comment, 8, 4096)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
config_t *config = obs_frontend_get_profile_config(); config_t *config = obs_frontend_get_profile_config();
if (changeFps) { if (changeFps) {
config_set_uint(config, "Video", "FPSType", 2); config_set_uint(config, "Video", "FPSType", 2);
config_set_uint(config, "Video", "FPSNum", request.RequestData["fpsNumerator"]); config_set_uint(config, "Video", "FPSNum",
config_set_uint(config, "Video", "FPSDen", request.RequestData["fpsDenominator"]); request.RequestData["fpsNumerator"]);
config_set_uint(config, "Video", "FPSDen",
request.RequestData["fpsDenominator"]);
} }
if (changeBaseRes) { if (changeBaseRes) {
config_set_uint(config, "Video", "BaseCX", request.RequestData["baseWidth"]); config_set_uint(config, "Video", "BaseCX",
config_set_uint(config, "Video", "BaseCY", request.RequestData["baseHeight"]); request.RequestData["baseWidth"]);
config_set_uint(config, "Video", "BaseCY",
request.RequestData["baseHeight"]);
} }
if (changeOutputRes) { if (changeOutputRes) {
config_set_uint(config, "Video", "OutputCX", request.RequestData["outputWidth"]); config_set_uint(config, "Video", "OutputCX",
config_set_uint(config, "Video", "OutputCY", request.RequestData["outputHeight"]); request.RequestData["outputWidth"]);
config_set_uint(config, "Video", "OutputCY",
request.RequestData["outputHeight"]);
} }
if (changeFps || changeBaseRes || changeOutputRes) { if (changeFps || changeBaseRes || changeOutputRes) {
@ -511,7 +611,9 @@ RequestResult RequestHandler::SetVideoSettings(const Request& request)
return RequestResult::Success(); return RequestResult::Success();
} }
return RequestResult::Error(RequestStatus::MissingRequestField, "You must specify at least one video-changing pair."); return RequestResult::Error(
RequestStatus::MissingRequestField,
"You must specify at least one video-changing pair.");
} }
/** /**
@ -527,14 +629,15 @@ RequestResult RequestHandler::SetVideoSettings(const Request& request)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetStreamServiceSettings(const Request&) RequestResult RequestHandler::GetStreamServiceSettings(const Request &)
{ {
json responseData; json responseData;
OBSService service = obs_frontend_get_streaming_service(); OBSService service = obs_frontend_get_streaming_service();
responseData["streamServiceType"] = obs_service_get_type(service); responseData["streamServiceType"] = obs_service_get_type(service);
OBSDataAutoRelease serviceSettings = obs_service_get_settings(service); OBSDataAutoRelease serviceSettings = obs_service_get_settings(service);
responseData["streamServiceSettings"] = Utils::Json::ObsDataToJson(serviceSettings, true); responseData["streamServiceSettings"] =
Utils::Json::ObsDataToJson(serviceSettings, true);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -554,38 +657,56 @@ RequestResult RequestHandler::GetStreamServiceSettings(const Request&)
* @category config * @category config
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetStreamServiceSettings(const Request& request) RequestResult RequestHandler::SetStreamServiceSettings(const Request &request)
{ {
if (obs_frontend_streaming_active()) if (obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::OutputRunning, "You cannot change stream service settings while streaming."); return RequestResult::Error(
RequestStatus::OutputRunning,
"You cannot change stream service settings while streaming.");
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!(request.ValidateString("streamServiceType", statusCode, comment) && request.ValidateObject("streamServiceSettings", statusCode, comment))) if (!(request.ValidateString("streamServiceType", statusCode,
comment) &&
request.ValidateObject("streamServiceSettings", statusCode,
comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSService currentStreamService = obs_frontend_get_streaming_service(); OBSService currentStreamService = obs_frontend_get_streaming_service();
std::string streamServiceType = obs_service_get_type(currentStreamService); std::string streamServiceType =
std::string requestedStreamServiceType = request.RequestData["streamServiceType"]; obs_service_get_type(currentStreamService);
OBSDataAutoRelease requestedStreamServiceSettings = Utils::Json::JsonToObsData(request.RequestData["streamServiceSettings"]); std::string requestedStreamServiceType =
request.RequestData["streamServiceType"];
OBSDataAutoRelease requestedStreamServiceSettings =
Utils::Json::JsonToObsData(
request.RequestData["streamServiceSettings"]);
// Don't create a new service if the current service is the same type. // Don't create a new service if the current service is the same type.
if (streamServiceType == requestedStreamServiceType) { if (streamServiceType == requestedStreamServiceType) {
OBSDataAutoRelease currentStreamServiceSettings = obs_service_get_settings(currentStreamService); OBSDataAutoRelease currentStreamServiceSettings =
obs_service_get_settings(currentStreamService);
// TODO: Add `overlay` field // TODO: Add `overlay` field
OBSDataAutoRelease newStreamServiceSettings = obs_data_create(); OBSDataAutoRelease newStreamServiceSettings = obs_data_create();
obs_data_apply(newStreamServiceSettings, currentStreamServiceSettings); obs_data_apply(newStreamServiceSettings,
obs_data_apply(newStreamServiceSettings, requestedStreamServiceSettings); currentStreamServiceSettings);
obs_data_apply(newStreamServiceSettings,
requestedStreamServiceSettings);
obs_service_update(currentStreamService, newStreamServiceSettings); obs_service_update(currentStreamService,
newStreamServiceSettings);
} else { } else {
// TODO: This leaks memory. I have no idea why. // TODO: This leaks memory. I have no idea why.
OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service", requestedStreamServiceSettings, nullptr); OBSService newStreamService = obs_service_create(
requestedStreamServiceType.c_str(),
"obs_websocket_custom_service",
requestedStreamServiceSettings, nullptr);
// TODO: Check service type here, instead of relying on service creation to fail. // TODO: Check service type here, instead of relying on service creation to fail.
if (!newStreamService) if (!newStreamService)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the stream service with the requested streamServiceType. It may be an invalid type."); return RequestResult::Error(
RequestStatus::ResourceCreationFailed,
"Failed to create the stream service with the requested streamServiceType. It may be an invalid type.");
obs_frontend_set_streaming_service(newStreamService); obs_frontend_set_streaming_service(newStreamService);
} }
@ -607,10 +728,11 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
* @api requests * @api requests
* @category rconfig * @category rconfig
*/ */
RequestResult RequestHandler::GetRecordDirectory(const Request&) RequestResult RequestHandler::GetRecordDirectory(const Request &)
{ {
json responseData; json responseData;
responseData["recordDirectory"] = Utils::Obs::StringHelper::GetCurrentRecordOutputPath(); responseData["recordDirectory"] =
Utils::Obs::StringHelper::GetCurrentRecordOutputPath();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }

View File

@ -33,16 +33,18 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::GetSourceFilterList(const Request& request) RequestResult RequestHandler::GetSourceFilterList(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source =
if(!source) request.ValidateSource("sourceName", statusCode, comment);
if (!source)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["filters"] = Utils::Obs::ArrayHelper::GetSourceFilterList(source); responseData["filters"] =
Utils::Obs::ArrayHelper::GetSourceFilterList(source);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -61,7 +63,8 @@ RequestResult RequestHandler::GetSourceFilterList(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request& request) RequestResult
RequestHandler::GetSourceFilterDefaultSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -73,12 +76,14 @@ RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request& requ
if (std::find(kinds.begin(), kinds.end(), filterKind) == kinds.end()) if (std::find(kinds.begin(), kinds.end(), filterKind) == kinds.end())
return RequestResult::Error(RequestStatus::InvalidFilterKind); return RequestResult::Error(RequestStatus::InvalidFilterKind);
OBSDataAutoRelease defaultSettings = obs_get_source_defaults(filterKind.c_str()); OBSDataAutoRelease defaultSettings =
obs_get_source_defaults(filterKind.c_str());
if (!defaultSettings) if (!defaultSettings)
return RequestResult::Error(RequestStatus::InvalidFilterKind); return RequestResult::Error(RequestStatus::InvalidFilterKind);
json responseData; json responseData;
responseData["defaultFilterSettings"] = Utils::Json::ObsDataToJson(defaultSettings, true); responseData["defaultFilterSettings"] =
Utils::Json::ObsDataToJson(defaultSettings, true);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -97,37 +102,51 @@ RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request& requ
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::CreateSourceFilter(const Request& request) RequestResult RequestHandler::CreateSourceFilter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source =
if (!(source && request.ValidateString("filterName", statusCode, comment) && request.ValidateString("filterKind", statusCode, comment))) request.ValidateSource("sourceName", statusCode, comment);
if (!(source &&
request.ValidateString("filterName", statusCode, comment) &&
request.ValidateString("filterKind", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string filterName = request.RequestData["filterName"]; std::string filterName = request.RequestData["filterName"];
OBSSourceAutoRelease existingFilter = obs_source_get_filter_by_name(source, filterName.c_str()); OBSSourceAutoRelease existingFilter =
obs_source_get_filter_by_name(source, filterName.c_str());
if (existingFilter) if (existingFilter)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A filter already exists by that name."); return RequestResult::Error(
RequestStatus::ResourceAlreadyExists,
"A filter already exists by that name.");
std::string filterKind = request.RequestData["filterKind"]; std::string filterKind = request.RequestData["filterKind"];
auto kinds = Utils::Obs::ArrayHelper::GetFilterKindList(); auto kinds = Utils::Obs::ArrayHelper::GetFilterKindList();
if (std::find(kinds.begin(), kinds.end(), filterKind) == kinds.end()) if (std::find(kinds.begin(), kinds.end(), filterKind) == kinds.end())
return RequestResult::Error(RequestStatus::InvalidFilterKind, "Your specified filter kind is not supported by OBS. Check that any necessary plugins are loaded."); return RequestResult::Error(
RequestStatus::InvalidFilterKind,
"Your specified filter kind is not supported by OBS. Check that any necessary plugins are loaded.");
OBSDataAutoRelease filterSettings = nullptr; OBSDataAutoRelease filterSettings = nullptr;
if (request.Contains("filterSettings")) { if (request.Contains("filterSettings")) {
if (!request.ValidateOptionalObject("filterSettings", statusCode, comment, true)) if (!request.ValidateOptionalObject("filterSettings",
statusCode, comment, true))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
filterSettings = Utils::Json::JsonToObsData(request.RequestData["filterSettings"]); filterSettings = Utils::Json::JsonToObsData(
request.RequestData["filterSettings"]);
} }
OBSSourceAutoRelease filter = Utils::Obs::ActionHelper::CreateSourceFilter(source, filterName, filterKind, filterSettings); OBSSourceAutoRelease filter =
Utils::Obs::ActionHelper::CreateSourceFilter(
source, filterName, filterKind, filterSettings);
if(!filter) if (!filter)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Creation of the filter failed."); return RequestResult::Error(
RequestStatus::ResourceCreationFailed,
"Creation of the filter failed.");
return RequestResult::Success(); return RequestResult::Success();
} }
@ -145,11 +164,12 @@ RequestResult RequestHandler::CreateSourceFilter(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::RemoveSourceFilter(const Request& request) RequestResult RequestHandler::RemoveSourceFilter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment); FilterPair pair = request.ValidateFilter("sourceName", "filterName",
statusCode, comment);
if (!pair.filter) if (!pair.filter)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -172,19 +192,24 @@ RequestResult RequestHandler::RemoveSourceFilter(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::SetSourceFilterName(const Request& request) RequestResult RequestHandler::SetSourceFilterName(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment); FilterPair pair = request.ValidateFilter("sourceName", "filterName",
if (!pair.filter || !request.ValidateString("newFilterName", statusCode, comment)) statusCode, comment);
if (!pair.filter ||
!request.ValidateString("newFilterName", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string newFilterName = request.RequestData["newFilterName"]; std::string newFilterName = request.RequestData["newFilterName"];
OBSSourceAutoRelease existingFilter = obs_source_get_filter_by_name(pair.source, newFilterName.c_str()); OBSSourceAutoRelease existingFilter = obs_source_get_filter_by_name(
pair.source, newFilterName.c_str());
if (existingFilter) if (existingFilter)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A filter already exists by that new name."); return RequestResult::Error(
RequestStatus::ResourceAlreadyExists,
"A filter already exists by that new name.");
obs_source_set_name(pair.filter, newFilterName.c_str()); obs_source_set_name(pair.filter, newFilterName.c_str());
@ -209,21 +234,27 @@ RequestResult RequestHandler::SetSourceFilterName(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::GetSourceFilter(const Request& request) RequestResult RequestHandler::GetSourceFilter(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment); FilterPair pair = request.ValidateFilter("sourceName", "filterName",
statusCode, comment);
if (!pair.filter) if (!pair.filter)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["filterEnabled"] = obs_source_enabled(pair.filter); responseData["filterEnabled"] = obs_source_enabled(pair.filter);
responseData["filterIndex"] = Utils::Obs::NumberHelper::GetSourceFilterIndex(pair.source, pair.filter); // Todo: Use `GetSourceFilterlist` to select this filter maybe responseData["filterIndex"] =
Utils::Obs::NumberHelper::GetSourceFilterIndex(
pair.source,
pair.filter); // Todo: Use `GetSourceFilterlist` to select this filter maybe
responseData["filterKind"] = obs_source_get_id(pair.filter); responseData["filterKind"] = obs_source_get_id(pair.filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(pair.filter); OBSDataAutoRelease filterSettings =
responseData["filterSettings"] = Utils::Json::ObsDataToJson(filterSettings); obs_source_get_settings(pair.filter);
responseData["filterSettings"] =
Utils::Json::ObsDataToJson(filterSettings);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -242,17 +273,20 @@ RequestResult RequestHandler::GetSourceFilter(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::SetSourceFilterIndex(const Request& request) RequestResult RequestHandler::SetSourceFilterIndex(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment); FilterPair pair = request.ValidateFilter("sourceName", "filterName",
if (!(pair.filter && request.ValidateNumber("filterIndex", statusCode, comment, 0, 8192))) statusCode, comment);
if (!(pair.filter && request.ValidateNumber("filterIndex", statusCode,
comment, 0, 8192)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
int filterIndex = request.RequestData["filterIndex"]; int filterIndex = request.RequestData["filterIndex"];
Utils::Obs::ActionHelper::SetSourceFilterIndex(pair.source, pair.filter, filterIndex); Utils::Obs::ActionHelper::SetSourceFilterIndex(pair.source, pair.filter,
filterIndex);
return RequestResult::Success(); return RequestResult::Success();
} }
@ -272,27 +306,33 @@ RequestResult RequestHandler::SetSourceFilterIndex(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::SetSourceFilterSettings(const Request& request) RequestResult RequestHandler::SetSourceFilterSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment); FilterPair pair = request.ValidateFilter("sourceName", "filterName",
if (!(pair.filter && request.ValidateObject("filterSettings", statusCode, comment, true))) statusCode, comment);
if (!(pair.filter && request.ValidateObject("filterSettings",
statusCode, comment, true)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
// Almost identical to SetInputSettings // Almost identical to SetInputSettings
bool overlay = true; bool overlay = true;
if (request.Contains("overlay")) { if (request.Contains("overlay")) {
if (!request.ValidateOptionalBoolean("overlay", statusCode, comment)) if (!request.ValidateOptionalBoolean("overlay", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
overlay = request.RequestData["overlay"]; overlay = request.RequestData["overlay"];
} }
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["filterSettings"]); OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(
request.RequestData["filterSettings"]);
if (!newSettings) if (!newSettings)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "An internal data conversion operation failed. Please report this!"); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"An internal data conversion operation failed. Please report this!");
if (overlay) if (overlay)
obs_source_update(pair.filter, newSettings); obs_source_update(pair.filter, newSettings);
@ -318,12 +358,14 @@ RequestResult RequestHandler::SetSourceFilterSettings(const Request& request)
* @api requests * @api requests
* @category filters * @category filters
*/ */
RequestResult RequestHandler::SetSourceFilterEnabled(const Request& request) RequestResult RequestHandler::SetSourceFilterEnabled(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment); FilterPair pair = request.ValidateFilter("sourceName", "filterName",
if (!(pair.filter && request.ValidateBoolean("filterEnabled", statusCode, comment))) statusCode, comment);
if (!(pair.filter &&
request.ValidateBoolean("filterEnabled", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
bool filterEnabled = request.RequestData["filterEnabled"]; bool filterEnabled = request.RequestData["filterEnabled"];

View File

@ -26,7 +26,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../WebSocketApi.h" #include "../WebSocketApi.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
/** /**
* Gets data about the current plugin and RPC version. * Gets data about the current plugin and RPC version.
* *
@ -45,7 +44,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetVersion(const Request&) RequestResult RequestHandler::GetVersion(const Request &)
{ {
json responseData; json responseData;
responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersion(); responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersion();
@ -53,15 +52,17 @@ RequestResult RequestHandler::GetVersion(const Request&)
responseData["rpcVersion"] = OBS_WEBSOCKET_RPC_VERSION; responseData["rpcVersion"] = OBS_WEBSOCKET_RPC_VERSION;
responseData["availableRequests"] = GetRequestList(); responseData["availableRequests"] = GetRequestList();
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats(); QList<QByteArray> imageWriterFormats =
QImageWriter::supportedImageFormats();
std::vector<std::string> supportedImageFormats; std::vector<std::string> supportedImageFormats;
for (const QByteArray& format : imageWriterFormats) { for (const QByteArray &format : imageWriterFormats) {
supportedImageFormats.push_back(format.toStdString()); supportedImageFormats.push_back(format.toStdString());
} }
responseData["supportedImageFormats"] = supportedImageFormats; responseData["supportedImageFormats"] = supportedImageFormats;
responseData["platform"] = QSysInfo::productType().toStdString(); responseData["platform"] = QSysInfo::productType().toStdString();
responseData["platformDescription"] = QSysInfo::prettyProductName().toStdString(); responseData["platformDescription"] =
QSysInfo::prettyProductName().toStdString();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -88,13 +89,15 @@ RequestResult RequestHandler::GetVersion(const Request&)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetStats(const Request&) RequestResult RequestHandler::GetStats(const Request &)
{ {
json responseData = Utils::Obs::ObjectHelper::GetStats(); json responseData = Utils::Obs::ObjectHelper::GetStats();
if (_session) { if (_session) {
responseData["webSocketSessionIncomingMessages"] = _session->IncomingMessages(); responseData["webSocketSessionIncomingMessages"] =
responseData["webSocketSessionOutgoingMessages"] = _session->OutgoingMessages(); _session->IncomingMessages();
responseData["webSocketSessionOutgoingMessages"] =
_session->OutgoingMessages();
} else { } else {
responseData["webSocketSessionIncomingMessages"] = nullptr; responseData["webSocketSessionIncomingMessages"] = nullptr;
responseData["webSocketSessionOutgoingMessages"] = nullptr; responseData["webSocketSessionOutgoingMessages"] = nullptr;
@ -115,7 +118,7 @@ RequestResult RequestHandler::GetStats(const Request&)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::BroadcastCustomEvent(const Request& request) RequestResult RequestHandler::BroadcastCustomEvent(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -124,9 +127,13 @@ RequestResult RequestHandler::BroadcastCustomEvent(const Request& request)
auto webSocketServer = GetWebSocketServer(); auto webSocketServer = GetWebSocketServer();
if (!webSocketServer) if (!webSocketServer)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Unable to send event due to internal error."); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Unable to send event due to internal error.");
webSocketServer->BroadcastEvent(EventSubscription::General, "CustomEvent", request.RequestData["eventData"]); webSocketServer->BroadcastEvent(EventSubscription::General,
"CustomEvent",
request.RequestData["eventData"]);
return RequestResult::Success(); return RequestResult::Success();
} }
@ -150,11 +157,12 @@ RequestResult RequestHandler::BroadcastCustomEvent(const Request& request)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::CallVendorRequest(const Request& request) RequestResult RequestHandler::CallVendorRequest(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!request.ValidateString("vendorName", statusCode, comment) || !request.ValidateString("requestType", statusCode, comment)) if (!request.ValidateString("vendorName", statusCode, comment) ||
!request.ValidateString("requestType", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string vendorName = request.RequestData["vendorName"]; std::string vendorName = request.RequestData["vendorName"];
@ -162,31 +170,41 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request)
OBSDataAutoRelease requestData = obs_data_create(); OBSDataAutoRelease requestData = obs_data_create();
if (request.Contains("requestData")) { if (request.Contains("requestData")) {
if (!request.ValidateOptionalObject("requestData", statusCode, comment)) if (!request.ValidateOptionalObject("requestData", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
requestData = Utils::Json::JsonToObsData(request.RequestData["requestData"]); requestData = Utils::Json::JsonToObsData(
request.RequestData["requestData"]);
} }
OBSDataAutoRelease obsResponseData = obs_data_create(); OBSDataAutoRelease obsResponseData = obs_data_create();
auto webSocketApi = GetWebSocketApi(); auto webSocketApi = GetWebSocketApi();
if (!webSocketApi) if (!webSocketApi)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Unable to call request due to internal error."); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Unable to call request due to internal error.");
auto ret = webSocketApi->PerformVendorRequest(vendorName, requestType, requestData, obsResponseData); auto ret = webSocketApi->PerformVendorRequest(
vendorName, requestType, requestData, obsResponseData);
switch (ret) { switch (ret) {
default: default:
case WebSocketApi::RequestReturnCode::Normal: case WebSocketApi::RequestReturnCode::Normal:
break; break;
case WebSocketApi::RequestReturnCode::NoVendor: case WebSocketApi::RequestReturnCode::NoVendor:
return RequestResult::Error(RequestStatus::ResourceNotFound, "No vendor was found by that name."); return RequestResult::Error(
case WebSocketApi::RequestReturnCode::NoVendorRequest: RequestStatus::ResourceNotFound,
return RequestResult::Error(RequestStatus::ResourceNotFound, "No request was found by that name."); "No vendor was found by that name.");
case WebSocketApi::RequestReturnCode::NoVendorRequest:
return RequestResult::Error(
RequestStatus::ResourceNotFound,
"No request was found by that name.");
} }
json responseData; json responseData;
responseData["responseData"] = Utils::Json::ObsDataToJson(obsResponseData); responseData["responseData"] =
Utils::Json::ObsDataToJson(obsResponseData);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -203,7 +221,7 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetHotkeyList(const Request&) RequestResult RequestHandler::GetHotkeyList(const Request &)
{ {
json responseData; json responseData;
responseData["hotkeys"] = Utils::Obs::ArrayHelper::GetHotkeyNameList(); responseData["hotkeys"] = Utils::Obs::ArrayHelper::GetHotkeyNameList();
@ -222,16 +240,19 @@ RequestResult RequestHandler::GetHotkeyList(const Request&)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::TriggerHotkeyByName(const Request& request) RequestResult RequestHandler::TriggerHotkeyByName(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!request.ValidateString("hotkeyName", statusCode, comment)) if (!request.ValidateString("hotkeyName", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
obs_hotkey_t *hotkey = Utils::Obs::SearchHelper::GetHotkeyByName(request.RequestData["hotkeyName"]); obs_hotkey_t *hotkey = Utils::Obs::SearchHelper::GetHotkeyByName(
request.RequestData["hotkeyName"]);
if (!hotkey) if (!hotkey)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No hotkeys were found by that name."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"No hotkeys were found by that name.");
obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hotkey), true); obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hotkey), true);
@ -255,7 +276,7 @@ RequestResult RequestHandler::TriggerHotkeyByName(const Request& request)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request) RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request &request)
{ {
obs_key_combination_t combo = {0}; obs_key_combination_t combo = {0};
@ -263,7 +284,8 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
std::string comment; std::string comment;
if (request.Contains("keyId")) { if (request.Contains("keyId")) {
if (!request.ValidateOptionalString("keyId", statusCode, comment)) if (!request.ValidateOptionalString("keyId", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string keyId = request.RequestData["keyId"]; std::string keyId = request.RequestData["keyId"];
@ -272,24 +294,37 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
statusCode = RequestStatus::NoError; statusCode = RequestStatus::NoError;
if (request.Contains("keyModifiers")) { if (request.Contains("keyModifiers")) {
if (!request.ValidateOptionalObject("keyModifiers", statusCode, comment, true)) if (!request.ValidateOptionalObject("keyModifiers", statusCode,
comment, true))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
const json keyModifiersJson = request.RequestData["keyModifiers"]; const json keyModifiersJson =
request.RequestData["keyModifiers"];
uint32_t keyModifiers = 0; uint32_t keyModifiers = 0;
if (keyModifiersJson.contains("shift") && keyModifiersJson["shift"].is_boolean() && keyModifiersJson["shift"].get<bool>()) if (keyModifiersJson.contains("shift") &&
keyModifiersJson["shift"].is_boolean() &&
keyModifiersJson["shift"].get<bool>())
keyModifiers |= INTERACT_SHIFT_KEY; keyModifiers |= INTERACT_SHIFT_KEY;
if (keyModifiersJson.contains("control") && keyModifiersJson["control"].is_boolean() && keyModifiersJson["control"].get<bool>()) if (keyModifiersJson.contains("control") &&
keyModifiersJson["control"].is_boolean() &&
keyModifiersJson["control"].get<bool>())
keyModifiers |= INTERACT_CONTROL_KEY; keyModifiers |= INTERACT_CONTROL_KEY;
if (keyModifiersJson.contains("alt") && keyModifiersJson["alt"].is_boolean() && keyModifiersJson["alt"].get<bool>()) if (keyModifiersJson.contains("alt") &&
keyModifiersJson["alt"].is_boolean() &&
keyModifiersJson["alt"].get<bool>())
keyModifiers |= INTERACT_ALT_KEY; keyModifiers |= INTERACT_ALT_KEY;
if (keyModifiersJson.contains("command") && keyModifiersJson["command"].is_boolean() && keyModifiersJson["command"].get<bool>()) if (keyModifiersJson.contains("command") &&
keyModifiersJson["command"].is_boolean() &&
keyModifiersJson["command"].get<bool>())
keyModifiers |= INTERACT_COMMAND_KEY; keyModifiers |= INTERACT_COMMAND_KEY;
combo.modifiers = keyModifiers; combo.modifiers = keyModifiers;
} }
if (!combo.modifiers && (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE)) if (!combo.modifiers &&
return RequestResult::Error(RequestStatus::CannotAct, "Your provided request fields cannot be used to trigger a hotkey."); (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE))
return RequestResult::Error(
RequestStatus::CannotAct,
"Your provided request fields cannot be used to trigger a hotkey.");
// Apparently things break when you don't start by setting the combo to false // Apparently things break when you don't start by setting the combo to false
obs_hotkey_inject_event(combo, false); obs_hotkey_inject_event(combo, false);
@ -312,24 +347,30 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
* @category general * @category general
* @api requests * @api requests
*/ */
RequestResult RequestHandler::Sleep(const Request& request) RequestResult RequestHandler::Sleep(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (request.ExecutionType == RequestBatchExecutionType::SerialRealtime) { if (request.ExecutionType ==
if (!request.ValidateNumber("sleepMillis", statusCode, comment, 0, 50000)) RequestBatchExecutionType::SerialRealtime) {
if (!request.ValidateNumber("sleepMillis", statusCode, comment,
0, 50000))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
int64_t sleepMillis = request.RequestData["sleepMillis"]; int64_t sleepMillis = request.RequestData["sleepMillis"];
std::this_thread::sleep_for(std::chrono::milliseconds(sleepMillis)); std::this_thread::sleep_for(
std::chrono::milliseconds(sleepMillis));
return RequestResult::Success(); return RequestResult::Success();
} else if (request.ExecutionType == RequestBatchExecutionType::SerialFrame) { } else if (request.ExecutionType ==
if (!request.ValidateNumber("sleepFrames", statusCode, comment, 0, 10000)) RequestBatchExecutionType::SerialFrame) {
if (!request.ValidateNumber("sleepFrames", statusCode, comment,
0, 10000))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
RequestResult ret = RequestResult::Success(); RequestResult ret = RequestResult::Success();
ret.SleepFrames = request.RequestData["sleepFrames"]; ret.SleepFrames = request.RequestData["sleepFrames"];
return ret; return ret;
} else { } else {
return RequestResult::Error(RequestStatus::UnsupportedRequestBatchExecutionType); return RequestResult::Error(
RequestStatus::UnsupportedRequestBatchExecutionType);
} }
} }

View File

@ -33,21 +33,23 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputList(const Request& request) RequestResult RequestHandler::GetInputList(const Request &request)
{ {
std::string inputKind; std::string inputKind;
if (request.Contains("inputKind")) { if (request.Contains("inputKind")) {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!request.ValidateOptionalString("inputKind", statusCode, comment)) if (!request.ValidateOptionalString("inputKind", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
inputKind = request.RequestData["inputKind"]; inputKind = request.RequestData["inputKind"];
} }
json responseData; json responseData;
responseData["inputs"] = Utils::Obs::ArrayHelper::GetInputList(inputKind); responseData["inputs"] =
Utils::Obs::ArrayHelper::GetInputList(inputKind);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -65,21 +67,23 @@ RequestResult RequestHandler::GetInputList(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputKindList(const Request& request) RequestResult RequestHandler::GetInputKindList(const Request &request)
{ {
bool unversioned = false; bool unversioned = false;
if (request.Contains("unversioned")) { if (request.Contains("unversioned")) {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!request.ValidateOptionalBoolean("unversioned", statusCode, comment)) if (!request.ValidateOptionalBoolean("unversioned", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
unversioned = request.RequestData["unversioned"]; unversioned = request.RequestData["unversioned"];
} }
json responseData; json responseData;
responseData["inputKinds"] = Utils::Obs::ArrayHelper::GetInputKindList(unversioned); responseData["inputKinds"] =
Utils::Obs::ArrayHelper::GetInputKindList(unversioned);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -100,11 +104,12 @@ RequestResult RequestHandler::GetInputKindList(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetSpecialInputs(const Request&) RequestResult RequestHandler::GetSpecialInputs(const Request &)
{ {
json responseData; json responseData;
std::vector<std::string> channels = {"desktop1", "desktop2", "mic1", "mic2", "mic3", "mic4"}; std::vector<std::string> channels = {"desktop1", "desktop2", "mic1",
"mic2", "mic3", "mic4"};
size_t channelId = 1; size_t channelId = 1;
for (auto &channel : channels) { for (auto &channel : channels) {
@ -138,47 +143,63 @@ RequestResult RequestHandler::GetSpecialInputs(const Request&)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::CreateInput(const Request& request) RequestResult RequestHandler::CreateInput(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease sceneSource = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease sceneSource =
if (!(sceneSource && request.ValidateString("inputName", statusCode, comment) && request.ValidateString("inputKind", statusCode, comment))) request.ValidateScene("sceneName", statusCode, comment);
if (!(sceneSource &&
request.ValidateString("inputName", statusCode, comment) &&
request.ValidateString("inputKind", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string inputName = request.RequestData["inputName"]; std::string inputName = request.RequestData["inputName"];
OBSSourceAutoRelease existingInput = obs_get_source_by_name(inputName.c_str()); OBSSourceAutoRelease existingInput =
obs_get_source_by_name(inputName.c_str());
if (existingInput) if (existingInput)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that input name."); return RequestResult::Error(
RequestStatus::ResourceAlreadyExists,
"A source already exists by that input name.");
std::string inputKind = request.RequestData["inputKind"]; std::string inputKind = request.RequestData["inputKind"];
auto kinds = Utils::Obs::ArrayHelper::GetInputKindList(); auto kinds = Utils::Obs::ArrayHelper::GetInputKindList();
if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end()) if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end())
return RequestResult::Error(RequestStatus::InvalidInputKind, "Your specified input kind is not supported by OBS. Check that your specified kind is properly versioned and that any necessary plugins are loaded."); return RequestResult::Error(
RequestStatus::InvalidInputKind,
"Your specified input kind is not supported by OBS. Check that your specified kind is properly versioned and that any necessary plugins are loaded.");
OBSDataAutoRelease inputSettings = nullptr; OBSDataAutoRelease inputSettings = nullptr;
if (request.Contains("inputSettings")) { if (request.Contains("inputSettings")) {
if (!request.ValidateOptionalObject("inputSettings", statusCode, comment, true)) if (!request.ValidateOptionalObject("inputSettings", statusCode,
comment, true))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
inputSettings = Utils::Json::JsonToObsData(request.RequestData["inputSettings"]); inputSettings = Utils::Json::JsonToObsData(
request.RequestData["inputSettings"]);
} }
OBSScene scene = obs_scene_from_source(sceneSource); OBSScene scene = obs_scene_from_source(sceneSource);
bool sceneItemEnabled = true; bool sceneItemEnabled = true;
if (request.Contains("sceneItemEnabled")) { if (request.Contains("sceneItemEnabled")) {
if (!request.ValidateOptionalBoolean("sceneItemEnabled", statusCode, comment)) if (!request.ValidateOptionalBoolean("sceneItemEnabled",
statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemEnabled = request.RequestData["sceneItemEnabled"]; sceneItemEnabled = request.RequestData["sceneItemEnabled"];
} }
// Create the input and add it as a scene item to the destination scene // Create the input and add it as a scene item to the destination scene
OBSSceneItemAutoRelease sceneItem = Utils::Obs::ActionHelper::CreateInput(inputName, inputKind, inputSettings, scene, sceneItemEnabled); OBSSceneItemAutoRelease sceneItem =
Utils::Obs::ActionHelper::CreateInput(inputName, inputKind,
inputSettings, scene,
sceneItemEnabled);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Creation of the input or scene item failed."); return RequestResult::Error(
RequestStatus::ResourceCreationFailed,
"Creation of the input or scene item failed.");
json responseData; json responseData;
responseData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); responseData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
@ -199,11 +220,12 @@ RequestResult RequestHandler::CreateInput(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::RemoveInput(const Request& request) RequestResult RequestHandler::RemoveInput(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -228,19 +250,24 @@ RequestResult RequestHandler::RemoveInput(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputName(const Request& request) RequestResult RequestHandler::SetInputName(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateString("newInputName", statusCode, comment))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateString("newInputName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string newInputName = request.RequestData["newInputName"]; std::string newInputName = request.RequestData["newInputName"];
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newInputName.c_str()); OBSSourceAutoRelease existingSource =
obs_get_source_by_name(newInputName.c_str());
if (existingSource) if (existingSource)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that new input name."); return RequestResult::Error(
RequestStatus::ResourceAlreadyExists,
"A source already exists by that new input name.");
obs_source_set_name(input, newInputName.c_str()); obs_source_set_name(input, newInputName.c_str());
@ -261,7 +288,7 @@ RequestResult RequestHandler::SetInputName(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputDefaultSettings(const Request& request) RequestResult RequestHandler::GetInputDefaultSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -273,12 +300,14 @@ RequestResult RequestHandler::GetInputDefaultSettings(const Request& request)
if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end()) if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end())
return RequestResult::Error(RequestStatus::InvalidInputKind); return RequestResult::Error(RequestStatus::InvalidInputKind);
OBSDataAutoRelease defaultSettings = obs_get_source_defaults(inputKind.c_str()); OBSDataAutoRelease defaultSettings =
obs_get_source_defaults(inputKind.c_str());
if (!defaultSettings) if (!defaultSettings)
return RequestResult::Error(RequestStatus::InvalidInputKind); return RequestResult::Error(RequestStatus::InvalidInputKind);
json responseData; json responseData;
responseData["defaultInputSettings"] = Utils::Json::ObsDataToJson(defaultSettings, true); responseData["defaultInputSettings"] =
Utils::Json::ObsDataToJson(defaultSettings, true);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -299,18 +328,20 @@ RequestResult RequestHandler::GetInputDefaultSettings(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputSettings(const Request& request) RequestResult RequestHandler::GetInputSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease inputSettings = obs_source_get_settings(input); OBSDataAutoRelease inputSettings = obs_source_get_settings(input);
json responseData; json responseData;
responseData["inputSettings"] = Utils::Json::ObsDataToJson(inputSettings); responseData["inputSettings"] =
Utils::Json::ObsDataToJson(inputSettings);
responseData["inputKind"] = obs_source_get_id(input); responseData["inputKind"] = obs_source_get_id(input);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -329,27 +360,33 @@ RequestResult RequestHandler::GetInputSettings(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputSettings(const Request& request) RequestResult RequestHandler::SetInputSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateObject("inputSettings", statusCode, comment, true))) request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateObject("inputSettings", statusCode,
comment, true)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
bool overlay = true; bool overlay = true;
if (request.Contains("overlay")) { if (request.Contains("overlay")) {
if (!request.ValidateOptionalBoolean("overlay", statusCode, comment)) if (!request.ValidateOptionalBoolean("overlay", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
overlay = request.RequestData["overlay"]; overlay = request.RequestData["overlay"];
} }
// Get the new settings and convert it to obs_data_t* // Get the new settings and convert it to obs_data_t*
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["inputSettings"]); OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(
request.RequestData["inputSettings"]);
if (!newSettings) if (!newSettings)
// This should never happen // This should never happen
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "An internal data conversion operation failed. Please report this!"); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"An internal data conversion operation failed. Please report this!");
if (overlay) if (overlay)
// Applies the new settings on top of the existing user settings // Applies the new settings on top of the existing user settings
@ -378,16 +415,19 @@ RequestResult RequestHandler::SetInputSettings(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputMute(const Request& request) RequestResult RequestHandler::GetInputMute(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
json responseData; json responseData;
responseData["inputMuted"] = obs_source_muted(input); responseData["inputMuted"] = obs_source_muted(input);
@ -407,16 +447,20 @@ RequestResult RequestHandler::GetInputMute(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputMute(const Request& request) RequestResult RequestHandler::SetInputMute(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateBoolean("inputMuted", statusCode, comment))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateBoolean("inputMuted", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
obs_source_set_muted(input, request.RequestData["inputMuted"]); obs_source_set_muted(input, request.RequestData["inputMuted"]);
@ -437,16 +481,19 @@ RequestResult RequestHandler::SetInputMute(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::ToggleInputMute(const Request& request) RequestResult RequestHandler::ToggleInputMute(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
bool inputMuted = !obs_source_muted(input); bool inputMuted = !obs_source_muted(input);
obs_source_set_muted(input, inputMuted); obs_source_set_muted(input, inputMuted);
@ -471,16 +518,19 @@ RequestResult RequestHandler::ToggleInputMute(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputVolume(const Request& request) RequestResult RequestHandler::GetInputVolume(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
float inputVolumeMul = obs_source_get_volume(input); float inputVolumeMul = obs_source_get_volume(input);
float inputVolumeDb = obs_mul_to_db(inputVolumeMul); float inputVolumeDb = obs_mul_to_db(inputVolumeMul);
@ -507,36 +557,46 @@ RequestResult RequestHandler::GetInputVolume(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputVolume(const Request& request) RequestResult RequestHandler::SetInputVolume(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
bool hasMul = request.Contains("inputVolumeMul"); bool hasMul = request.Contains("inputVolumeMul");
if (hasMul && !request.ValidateOptionalNumber("inputVolumeMul", statusCode, comment, 0, 20)) if (hasMul && !request.ValidateOptionalNumber(
"inputVolumeMul", statusCode, comment, 0, 20))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
bool hasDb = request.Contains("inputVolumeDb"); bool hasDb = request.Contains("inputVolumeDb");
if (hasDb && !request.ValidateOptionalNumber("inputVolumeDb", statusCode, comment, -100, 26)) if (hasDb && !request.ValidateOptionalNumber(
"inputVolumeDb", statusCode, comment, -100, 26))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (hasMul && hasDb) if (hasMul && hasDb)
return RequestResult::Error(RequestStatus::TooManyRequestFields, "You may only specify one volume field."); return RequestResult::Error(
RequestStatus::TooManyRequestFields,
"You may only specify one volume field.");
if (!hasMul && !hasDb) if (!hasMul && !hasDb)
return RequestResult::Error(RequestStatus::MissingRequestField, "You must specify one volume field."); return RequestResult::Error(
RequestStatus::MissingRequestField,
"You must specify one volume field.");
float inputVolumeMul; float inputVolumeMul;
if (hasMul) if (hasMul)
inputVolumeMul = request.RequestData["inputVolumeMul"]; inputVolumeMul = request.RequestData["inputVolumeMul"];
else else
inputVolumeMul = obs_db_to_mul(request.RequestData["inputVolumeDb"]); inputVolumeMul =
obs_db_to_mul(request.RequestData["inputVolumeDb"]);
obs_source_set_volume(input, inputVolumeMul); obs_source_set_volume(input, inputVolumeMul);
@ -557,16 +617,19 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputAudioBalance(const Request& request) RequestResult RequestHandler::GetInputAudioBalance(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
json responseData; json responseData;
responseData["inputAudioBalance"] = obs_source_get_balance_value(input); responseData["inputAudioBalance"] = obs_source_get_balance_value(input);
@ -587,16 +650,20 @@ RequestResult RequestHandler::GetInputAudioBalance(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputAudioBalance(const Request& request) RequestResult RequestHandler::SetInputAudioBalance(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateNumber("inputAudioBalance", statusCode, comment, 0.0, 1.0))) request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateNumber("inputAudioBalance", statusCode,
comment, 0.0, 1.0)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
float inputAudioBalance = request.RequestData["inputAudioBalance"]; float inputAudioBalance = request.RequestData["inputAudioBalance"];
obs_source_set_balance_value(input, inputAudioBalance); obs_source_set_balance_value(input, inputAudioBalance);
@ -620,20 +687,24 @@ RequestResult RequestHandler::SetInputAudioBalance(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request) RequestResult RequestHandler::GetInputAudioSyncOffset(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
json responseData; json responseData;
// Offset is stored in nanoseconds in OBS. // Offset is stored in nanoseconds in OBS.
responseData["inputAudioSyncOffset"] = obs_source_get_sync_offset(input) / 1000000; responseData["inputAudioSyncOffset"] =
obs_source_get_sync_offset(input) / 1000000;
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -651,16 +722,21 @@ RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request) RequestResult RequestHandler::SetInputAudioSyncOffset(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateNumber("inputAudioSyncOffset", statusCode, comment, -950, 20000))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateNumber("inputAudioSyncOffset", statusCode,
comment, -950, 20000)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
int64_t syncOffset = request.RequestData["inputAudioSyncOffset"]; int64_t syncOffset = request.RequestData["inputAudioSyncOffset"];
obs_source_set_sync_offset(input, syncOffset * 1000000); obs_source_set_sync_offset(input, syncOffset * 1000000);
@ -688,19 +764,23 @@ RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request) RequestResult RequestHandler::GetInputAudioMonitorType(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
json responseData; json responseData;
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input); responseData["monitorType"] =
Utils::Obs::StringHelper::GetInputMonitorType(input);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -718,19 +798,25 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request) RequestResult RequestHandler::SetInputAudioMonitorType(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateString("monitorType", statusCode, comment))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateString("monitorType", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
if (!obs_audio_monitoring_available()) if (!obs_audio_monitoring_available())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Audio monitoring is not available on this platform."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"Audio monitoring is not available on this platform.");
enum obs_monitoring_type monitorType; enum obs_monitoring_type monitorType;
std::string monitorTypeString = request.RequestData["monitorType"]; std::string monitorTypeString = request.RequestData["monitorType"];
@ -741,7 +827,10 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
else if (monitorTypeString == "OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT") else if (monitorTypeString == "OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT")
monitorType = OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT; monitorType = OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT;
else else
return RequestResult::Error(RequestStatus::InvalidRequestField, std::string("Unknown monitor type: ") + monitorTypeString); return RequestResult::Error(
RequestStatus::InvalidRequestField,
std::string("Unknown monitor type: ") +
monitorTypeString);
obs_source_set_monitoring_type(input, monitorType); obs_source_set_monitoring_type(input, monitorType);
@ -762,22 +851,26 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputAudioTracks(const Request& request) RequestResult RequestHandler::GetInputAudioTracks(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
long long tracks = obs_source_get_audio_mixers(input); long long tracks = obs_source_get_audio_mixers(input);
json inputAudioTracks; json inputAudioTracks;
for (long long i = 0; i < MAX_AUDIO_MIXES; i++) { for (long long i = 0; i < MAX_AUDIO_MIXES; i++) {
inputAudioTracks[std::to_string(i + 1)] = (bool)((tracks >> i) & 1); inputAudioTracks[std::to_string(i + 1)] =
(bool)((tracks >> i) & 1);
} }
json responseData; json responseData;
@ -799,16 +892,20 @@ RequestResult RequestHandler::GetInputAudioTracks(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::SetInputAudioTracks(const Request& request) RequestResult RequestHandler::SetInputAudioTracks(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!input || !request.ValidateObject("inputAudioTracks", statusCode, comment)) request.ValidateInput("inputName", statusCode, comment);
if (!input ||
!request.ValidateObject("inputAudioTracks", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support audio.");
json inputAudioTracks = request.RequestData["inputAudioTracks"]; json inputAudioTracks = request.RequestData["inputAudioTracks"];
@ -821,7 +918,9 @@ RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
continue; continue;
if (!inputAudioTracks[track].is_boolean()) if (!inputAudioTracks[track].is_boolean())
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The value of one of your tracks is not a boolean."); return RequestResult::Error(
RequestStatus::InvalidRequestFieldType,
"The value of one of your tracks is not a boolean.");
bool enabled = inputAudioTracks[track]; bool enabled = inputAudioTracks[track];
@ -854,25 +953,34 @@ RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request& request) RequestResult
RequestHandler::GetInputPropertiesListPropertyItems(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateString("propertyName", statusCode, comment))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateString("propertyName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string propertyName = request.RequestData["propertyName"]; std::string propertyName = request.RequestData["propertyName"];
OBSPropertiesAutoDestroy inputProperties = obs_source_properties(input); OBSPropertiesAutoDestroy inputProperties = obs_source_properties(input);
obs_property_t *property = obs_properties_get(inputProperties, propertyName.c_str()); obs_property_t *property =
obs_properties_get(inputProperties, propertyName.c_str());
if (!property) if (!property)
return RequestResult::Error(RequestStatus::ResourceNotFound, "Unable to find a property by that name."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"Unable to find a property by that name.");
if (obs_property_get_type(property) != OBS_PROPERTY_LIST) if (obs_property_get_type(property) != OBS_PROPERTY_LIST)
return RequestResult::Error(RequestStatus::InvalidResourceType, "The property found is not a list."); return RequestResult::Error(
RequestStatus::InvalidResourceType,
"The property found is not a list.");
json responseData; json responseData;
responseData["propertyItems"] = Utils::Obs::ArrayHelper::GetListPropertyItems(property); responseData["propertyItems"] =
Utils::Obs::ArrayHelper::GetListPropertyItems(property);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -892,24 +1000,33 @@ RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request&
* @api requests * @api requests
* @category inputs * @category inputs
*/ */
RequestResult RequestHandler::PressInputPropertiesButton(const Request& request) RequestResult RequestHandler::PressInputPropertiesButton(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateString("propertyName", statusCode, comment))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateString("propertyName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string propertyName = request.RequestData["propertyName"]; std::string propertyName = request.RequestData["propertyName"];
OBSPropertiesAutoDestroy inputProperties = obs_source_properties(input); OBSPropertiesAutoDestroy inputProperties = obs_source_properties(input);
obs_property_t *property = obs_properties_get(inputProperties, propertyName.c_str()); obs_property_t *property =
obs_properties_get(inputProperties, propertyName.c_str());
if (!property) if (!property)
return RequestResult::Error(RequestStatus::ResourceNotFound, "Unable to find a property by that name."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"Unable to find a property by that name.");
if (obs_property_get_type(property) != OBS_PROPERTY_BUTTON) if (obs_property_get_type(property) != OBS_PROPERTY_BUTTON)
return RequestResult::Error(RequestStatus::InvalidResourceType, "The property found is not a button."); return RequestResult::Error(
RequestStatus::InvalidResourceType,
"The property found is not a button.");
if (!obs_property_enabled(property)) if (!obs_property_enabled(property))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The property item found is not enabled."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The property item found is not enabled.");
obs_property_button_clicked(property, input); obs_property_button_clicked(property, input);

View File

@ -22,7 +22,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
bool IsMediaTimeValid(obs_source_t *input) bool IsMediaTimeValid(obs_source_t *input)
{ {
auto mediaState = obs_source_media_get_state(input); auto mediaState = obs_source_media_get_state(input);
return mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED; return mediaState == OBS_MEDIA_STATE_PLAYING ||
mediaState == OBS_MEDIA_STATE_PAUSED;
} }
/** /**
@ -52,19 +53,22 @@ bool IsMediaTimeValid(obs_source_t *input)
* @api requests * @api requests
* @category media inputs * @category media inputs
*/ */
RequestResult RequestHandler::GetMediaInputStatus(const Request& request) RequestResult RequestHandler::GetMediaInputStatus(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["mediaState"] = Utils::Obs::StringHelper::GetMediaInputState(input); responseData["mediaState"] =
Utils::Obs::StringHelper::GetMediaInputState(input);
if (IsMediaTimeValid(input)) { if (IsMediaTimeValid(input)) {
responseData["mediaDuration"] = obs_source_media_get_duration(input); responseData["mediaDuration"] =
obs_source_media_get_duration(input);
responseData["mediaCursor"] = obs_source_media_get_time(input); responseData["mediaCursor"] = obs_source_media_get_time(input);
} else { } else {
responseData["mediaDuration"] = nullptr; responseData["mediaDuration"] = nullptr;
@ -89,16 +93,20 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
* @api requests * @api requests
* @category media inputs * @category media inputs
*/ */
RequestResult RequestHandler::SetMediaInputCursor(const Request& request) RequestResult RequestHandler::SetMediaInputCursor(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateNumber("mediaCursor", statusCode, comment, 0))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateNumber("mediaCursor", statusCode, comment, 0)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!IsMediaTimeValid(input)) if (!IsMediaTimeValid(input))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The media input must be playing or paused in order to set the cursor position."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The media input must be playing or paused in order to set the cursor position.");
int64_t mediaCursor = request.RequestData["mediaCursor"]; int64_t mediaCursor = request.RequestData["mediaCursor"];
@ -123,19 +131,24 @@ RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
* @api requests * @api requests
* @category media inputs * @category media inputs
*/ */
RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request) RequestResult RequestHandler::OffsetMediaInputCursor(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateNumber("mediaCursorOffset", statusCode, comment))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateNumber("mediaCursorOffset", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!IsMediaTimeValid(input)) if (!IsMediaTimeValid(input))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The media input must be playing or paused in order to set the cursor position."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The media input must be playing or paused in order to set the cursor position.");
int64_t mediaCursorOffset = request.RequestData["mediaCursorOffset"]; int64_t mediaCursorOffset = request.RequestData["mediaCursorOffset"];
int64_t mediaCursor = obs_source_media_get_time(input) + mediaCursorOffset; int64_t mediaCursor =
obs_source_media_get_time(input) + mediaCursorOffset;
if (mediaCursor < 0) if (mediaCursor < 0)
mediaCursor = 0; mediaCursor = 0;
@ -158,41 +171,46 @@ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
* @api requests * @api requests
* @category media inputs * @category media inputs
*/ */
RequestResult RequestHandler::TriggerMediaInputAction(const Request& request) RequestResult RequestHandler::TriggerMediaInputAction(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
if (!(input && request.ValidateString("mediaAction", statusCode, comment))) request.ValidateInput("inputName", statusCode, comment);
if (!(input &&
request.ValidateString("mediaAction", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string mediaActionString = request.RequestData["mediaAction"]; std::string mediaActionString = request.RequestData["mediaAction"];
auto mediaAction = Utils::Obs::EnumHelper::GetMediaInputAction(mediaActionString); auto mediaAction =
Utils::Obs::EnumHelper::GetMediaInputAction(mediaActionString);
switch (mediaAction) { switch (mediaAction) {
default: default:
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE: case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE:
return RequestResult::Error(RequestStatus::InvalidRequestField, "You have specified an invalid media input action."); return RequestResult::Error(
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY: RequestStatus::InvalidRequestField,
// Shoutout to whoever implemented this API call like this "You have specified an invalid media input action.");
obs_source_media_play_pause(input, false); case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY:
break; // Shoutout to whoever implemented this API call like this
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE: obs_source_media_play_pause(input, false);
obs_source_media_play_pause(input, true); break;
break; case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE:
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP: obs_source_media_play_pause(input, true);
obs_source_media_stop(input); break;
break; case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP:
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART: obs_source_media_stop(input);
// I'm only implementing this because I'm nice. I think its a really dumb action. break;
obs_source_media_restart(input); case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART:
break; // I'm only implementing this because I'm nice. I think its a really dumb action.
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT: obs_source_media_restart(input);
obs_source_media_next(input); break;
break; case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT:
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS: obs_source_media_next(input);
obs_source_media_previous(input); break;
break; case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS:
obs_source_media_previous(input);
break;
} }
return RequestResult::Success(); return RequestResult::Success();

View File

@ -46,10 +46,11 @@ static bool ReplayBufferAvailable()
* @category outputs * @category outputs
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetVirtualCamStatus(const Request&) RequestResult RequestHandler::GetVirtualCamStatus(const Request &)
{ {
if (!VirtualCamAvailable()) if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"VirtualCam is not available.");
json responseData; json responseData;
responseData["outputActive"] = obs_frontend_virtualcam_active(); responseData["outputActive"] = obs_frontend_virtualcam_active();
@ -68,10 +69,11 @@ RequestResult RequestHandler::GetVirtualCamStatus(const Request&)
* @category outputs * @category outputs
* @api requests * @api requests
*/ */
RequestResult RequestHandler::ToggleVirtualCam(const Request&) RequestResult RequestHandler::ToggleVirtualCam(const Request &)
{ {
if (!VirtualCamAvailable()) if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"VirtualCam is not available.");
bool outputActive = obs_frontend_virtualcam_active(); bool outputActive = obs_frontend_virtualcam_active();
@ -95,10 +97,11 @@ RequestResult RequestHandler::ToggleVirtualCam(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::StartVirtualCam(const Request&) RequestResult RequestHandler::StartVirtualCam(const Request &)
{ {
if (!VirtualCamAvailable()) if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"VirtualCam is not available.");
if (obs_frontend_virtualcam_active()) if (obs_frontend_virtualcam_active())
return RequestResult::Error(RequestStatus::OutputRunning); return RequestResult::Error(RequestStatus::OutputRunning);
@ -118,10 +121,11 @@ RequestResult RequestHandler::StartVirtualCam(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::StopVirtualCam(const Request&) RequestResult RequestHandler::StopVirtualCam(const Request &)
{ {
if (!VirtualCamAvailable()) if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"VirtualCam is not available.");
if (!obs_frontend_virtualcam_active()) if (!obs_frontend_virtualcam_active())
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
@ -143,10 +147,11 @@ RequestResult RequestHandler::StopVirtualCam(const Request&)
* @category outputs * @category outputs
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetReplayBufferStatus(const Request&) RequestResult RequestHandler::GetReplayBufferStatus(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"Replay buffer is not available.");
json responseData; json responseData;
responseData["outputActive"] = obs_frontend_replay_buffer_active(); responseData["outputActive"] = obs_frontend_replay_buffer_active();
@ -165,10 +170,11 @@ RequestResult RequestHandler::GetReplayBufferStatus(const Request&)
* @category outputs * @category outputs
* @api requests * @api requests
*/ */
RequestResult RequestHandler::ToggleReplayBuffer(const Request&) RequestResult RequestHandler::ToggleReplayBuffer(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"Replay buffer is not available.");
bool outputActive = obs_frontend_replay_buffer_active(); bool outputActive = obs_frontend_replay_buffer_active();
@ -192,10 +198,11 @@ RequestResult RequestHandler::ToggleReplayBuffer(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::StartReplayBuffer(const Request&) RequestResult RequestHandler::StartReplayBuffer(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"Replay buffer is not available.");
if (obs_frontend_replay_buffer_active()) if (obs_frontend_replay_buffer_active())
return RequestResult::Error(RequestStatus::OutputRunning); return RequestResult::Error(RequestStatus::OutputRunning);
@ -215,10 +222,11 @@ RequestResult RequestHandler::StartReplayBuffer(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::StopReplayBuffer(const Request&) RequestResult RequestHandler::StopReplayBuffer(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"Replay buffer is not available.");
if (!obs_frontend_replay_buffer_active()) if (!obs_frontend_replay_buffer_active())
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
@ -238,10 +246,11 @@ RequestResult RequestHandler::StopReplayBuffer(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::SaveReplayBuffer(const Request&) RequestResult RequestHandler::SaveReplayBuffer(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"Replay buffer is not available.");
if (!obs_frontend_replay_buffer_active()) if (!obs_frontend_replay_buffer_active())
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
@ -263,15 +272,17 @@ RequestResult RequestHandler::SaveReplayBuffer(const Request&)
* @api requests * @api requests
* @category outputs * @category outputs
*/ */
RequestResult RequestHandler::GetLastReplayBufferReplay(const Request&) RequestResult RequestHandler::GetLastReplayBufferReplay(const Request &)
{ {
if (!ReplayBufferAvailable()) if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available."); return RequestResult::Error(RequestStatus::InvalidResourceState,
"Replay buffer is not available.");
if (!obs_frontend_replay_buffer_active()) if (!obs_frontend_replay_buffer_active())
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
json responseData; json responseData;
responseData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFilePath(); responseData["savedReplayPath"] =
Utils::Obs::StringHelper::GetLastReplayBufferFilePath();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }

View File

@ -35,18 +35,21 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::GetRecordStatus(const Request&) RequestResult RequestHandler::GetRecordStatus(const Request &)
{ {
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output(); OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(recordOutput); uint64_t outputDuration =
Utils::Obs::NumberHelper::GetOutputDuration(recordOutput);
json responseData; json responseData;
responseData["outputActive"] = obs_output_active(recordOutput); responseData["outputActive"] = obs_output_active(recordOutput);
responseData["outputPaused"] = obs_output_paused(recordOutput); responseData["outputPaused"] = obs_output_paused(recordOutput);
responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration); responseData["outputTimecode"] =
Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
responseData["outputDuration"] = outputDuration; responseData["outputDuration"] = outputDuration;
responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(recordOutput); responseData["outputBytes"] =
(uint64_t)obs_output_get_total_bytes(recordOutput);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -61,7 +64,7 @@ RequestResult RequestHandler::GetRecordStatus(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::ToggleRecord(const Request&) RequestResult RequestHandler::ToggleRecord(const Request &)
{ {
json responseData; json responseData;
if (obs_frontend_recording_active()) { if (obs_frontend_recording_active()) {
@ -85,7 +88,7 @@ RequestResult RequestHandler::ToggleRecord(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::StartRecord(const Request&) RequestResult RequestHandler::StartRecord(const Request &)
{ {
if (obs_frontend_recording_active()) if (obs_frontend_recording_active())
return RequestResult::Error(RequestStatus::OutputRunning); return RequestResult::Error(RequestStatus::OutputRunning);
@ -106,7 +109,7 @@ RequestResult RequestHandler::StartRecord(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::StopRecord(const Request&) RequestResult RequestHandler::StopRecord(const Request &)
{ {
if (!obs_frontend_recording_active()) if (!obs_frontend_recording_active())
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
@ -127,7 +130,7 @@ RequestResult RequestHandler::StopRecord(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::ToggleRecordPause(const Request&) RequestResult RequestHandler::ToggleRecordPause(const Request &)
{ {
json responseData; json responseData;
if (obs_frontend_recording_paused()) { if (obs_frontend_recording_paused()) {
@ -151,7 +154,7 @@ RequestResult RequestHandler::ToggleRecordPause(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::PauseRecord(const Request&) RequestResult RequestHandler::PauseRecord(const Request &)
{ {
if (obs_frontend_recording_paused()) if (obs_frontend_recording_paused())
return RequestResult::Error(RequestStatus::OutputPaused); return RequestResult::Error(RequestStatus::OutputPaused);
@ -172,7 +175,7 @@ RequestResult RequestHandler::PauseRecord(const Request&)
* @api requests * @api requests
* @category record * @category record
*/ */
RequestResult RequestHandler::ResumeRecord(const Request&) RequestResult RequestHandler::ResumeRecord(const Request &)
{ {
if (!obs_frontend_recording_paused()) if (!obs_frontend_recording_paused())
return RequestResult::Error(RequestStatus::OutputNotPaused); return RequestResult::Error(RequestStatus::OutputNotPaused);

View File

@ -35,16 +35,18 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemList(const Request& request) RequestResult RequestHandler::GetSceneItemList(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease scene =
request.ValidateScene("sceneName", statusCode, comment);
if (!scene) if (!scene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_from_source(scene)); responseData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(
obs_scene_from_source(scene));
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -67,16 +69,19 @@ RequestResult RequestHandler::GetSceneItemList(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetGroupSceneItemList(const Request& request) RequestResult RequestHandler::GetGroupSceneItemList(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY); OBSSourceAutoRelease scene =
request.ValidateScene("sceneName", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY);
if (!scene) if (!scene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(obs_group_from_source(scene)); responseData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(
obs_group_from_source(scene));
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -99,26 +104,34 @@ RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemId(const Request& request) RequestResult RequestHandler::GetSceneItemId(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneAutoRelease scene = request.ValidateScene2("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneAutoRelease scene = request.ValidateScene2(
if (!(scene && request.ValidateString("sourceName", statusCode, comment))) "sceneName", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(scene &&
request.ValidateString("sourceName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string sourceName = request.RequestData["sourceName"]; std::string sourceName = request.RequestData["sourceName"];
int offset = 0; int offset = 0;
if (request.Contains("searchOffset")) { if (request.Contains("searchOffset")) {
if (!request.ValidateOptionalNumber("searchOffset", statusCode, comment, -1)) if (!request.ValidateOptionalNumber("searchOffset", statusCode,
comment, -1))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
offset = request.RequestData["searchOffset"]; offset = request.RequestData["searchOffset"];
} }
OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName, offset); OBSSceneItemAutoRelease item =
Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName,
offset);
if (!item) if (!item)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene items were found in the specified scene by that name or offset."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"No scene items were found in the specified scene by that name or offset.");
json responseData; json responseData;
responseData["sceneItemId"] = obs_sceneitem_get_id(item); responseData["sceneItemId"] = obs_sceneitem_get_id(item);
@ -144,33 +157,43 @@ RequestResult RequestHandler::GetSceneItemId(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::CreateSceneItem(const Request& request) RequestResult RequestHandler::CreateSceneItem(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease sceneSource = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease sceneSource =
request.ValidateScene("sceneName", statusCode, comment);
if (!sceneSource) if (!sceneSource)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSScene scene = obs_scene_from_source(sceneSource); OBSScene scene = obs_scene_from_source(sceneSource);
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source =
request.ValidateSource("sourceName", statusCode, comment);
if (!source) if (!source)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (request.RequestData["sceneName"] == request.RequestData["sourceName"]) if (request.RequestData["sceneName"] ==
return RequestResult::Error(RequestStatus::CannotAct, "You cannot create scene item of a scene within itself."); request.RequestData["sourceName"])
return RequestResult::Error(
RequestStatus::CannotAct,
"You cannot create scene item of a scene within itself.");
bool sceneItemEnabled = true; bool sceneItemEnabled = true;
if (request.Contains("sceneItemEnabled")) { if (request.Contains("sceneItemEnabled")) {
if (!request.ValidateOptionalBoolean("sceneItemEnabled", statusCode, comment)) if (!request.ValidateOptionalBoolean("sceneItemEnabled",
statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemEnabled = request.RequestData["sceneItemEnabled"]; sceneItemEnabled = request.RequestData["sceneItemEnabled"];
} }
OBSSceneItemAutoRelease sceneItem = Utils::Obs::ActionHelper::CreateSceneItem(source, scene, sceneItemEnabled); OBSSceneItemAutoRelease sceneItem =
Utils::Obs::ActionHelper::CreateSceneItem(source, scene,
sceneItemEnabled);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene item."); return RequestResult::Error(
RequestStatus::ResourceCreationFailed,
"Failed to create the scene item.");
json responseData; json responseData;
responseData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); responseData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
@ -193,11 +216,12 @@ RequestResult RequestHandler::CreateSceneItem(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::RemoveSceneItem(const Request& request) RequestResult RequestHandler::RemoveSceneItem(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
"sceneName", "sceneItemId", statusCode, comment);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -225,29 +249,37 @@ RequestResult RequestHandler::RemoveSceneItem(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::DuplicateSceneItem(const Request& request) RequestResult RequestHandler::DuplicateSceneItem(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
"sceneName", "sceneItemId", statusCode, comment);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
// Get destination scene // Get destination scene
obs_scene_t *destinationScene; obs_scene_t *destinationScene;
if (request.Contains("destinationSceneName")) { if (request.Contains("destinationSceneName")) {
destinationScene = request.ValidateScene2("destinationSceneName", statusCode, comment); destinationScene = request.ValidateScene2(
"destinationSceneName", statusCode, comment);
if (!destinationScene) if (!destinationScene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
} else { } else {
destinationScene = obs_scene_get_ref(obs_sceneitem_get_scene(sceneItem)); destinationScene =
obs_scene_get_ref(obs_sceneitem_get_scene(sceneItem));
if (!destinationScene) if (!destinationScene)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Internal error: Failed to get ref for scene of scene item."); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Internal error: Failed to get ref for scene of scene item.");
} }
if (obs_sceneitem_is_group(sceneItem) && obs_sceneitem_get_scene(sceneItem) == destinationScene) { if (obs_sceneitem_is_group(sceneItem) &&
obs_sceneitem_get_scene(sceneItem) == destinationScene) {
obs_scene_release(destinationScene); obs_scene_release(destinationScene);
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Scenes may only have one instance of a group."); return RequestResult::Error(
RequestStatus::ResourceCreationFailed,
"Scenes may only have one instance of a group.");
} }
// Get scene item details // Get scene item details
@ -259,10 +291,15 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
obs_sceneitem_get_crop(sceneItem, &sceneItemCrop); obs_sceneitem_get_crop(sceneItem, &sceneItemCrop);
// Create the new item // Create the new item
OBSSceneItemAutoRelease newSceneItem = Utils::Obs::ActionHelper::CreateSceneItem(sceneItemSource, destinationScene, sceneItemEnabled, &sceneItemTransform, &sceneItemCrop); OBSSceneItemAutoRelease newSceneItem =
Utils::Obs::ActionHelper::CreateSceneItem(
sceneItemSource, destinationScene, sceneItemEnabled,
&sceneItemTransform, &sceneItemCrop);
obs_scene_release(destinationScene); obs_scene_release(destinationScene);
if (!newSceneItem) if (!newSceneItem)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene item."); return RequestResult::Error(
RequestStatus::ResourceCreationFailed,
"Failed to create the scene item.");
json responseData; json responseData;
responseData["sceneItemId"] = obs_sceneitem_get_id(newSceneItem); responseData["sceneItemId"] = obs_sceneitem_get_id(newSceneItem);
@ -287,16 +324,19 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemTransform(const Request& request) RequestResult RequestHandler::GetSceneItemTransform(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
"sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["sceneItemTransform"] = Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem); responseData["sceneItemTransform"] =
Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -315,12 +355,15 @@ RequestResult RequestHandler::GetSceneItemTransform(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemTransform(const Request& request) RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
if (!(sceneItem && request.ValidateObject("sceneItemTransform", statusCode, comment))) "sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateObject("sceneItemTransform",
statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
// Create a fake request to use checks on the sub object // Create a fake request to use checks on the sub object
@ -338,20 +381,23 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
float sourceHeight = float(obs_source_get_height(source)); float sourceHeight = float(obs_source_get_height(source));
if (r.Contains("positionX")) { if (r.Contains("positionX")) {
if (!r.ValidateOptionalNumber("positionX", statusCode, comment, -90001.0, 90001.0)) if (!r.ValidateOptionalNumber("positionX", statusCode, comment,
-90001.0, 90001.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemTransform.pos.x = r.RequestData["positionX"]; sceneItemTransform.pos.x = r.RequestData["positionX"];
transformChanged = true; transformChanged = true;
} }
if (r.Contains("positionY")) { if (r.Contains("positionY")) {
if (!r.ValidateOptionalNumber("positionY", statusCode, comment, -90001.0, 90001.0)) if (!r.ValidateOptionalNumber("positionY", statusCode, comment,
-90001.0, 90001.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemTransform.pos.y = r.RequestData["positionY"]; sceneItemTransform.pos.y = r.RequestData["positionY"];
transformChanged = true; transformChanged = true;
} }
if (r.Contains("rotation")) { if (r.Contains("rotation")) {
if (!r.ValidateOptionalNumber("rotation", statusCode, comment, -360.0, 360.0)) if (!r.ValidateOptionalNumber("rotation", statusCode, comment,
-360.0, 360.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemTransform.rot = r.RequestData["rotation"]; sceneItemTransform.rot = r.RequestData["rotation"];
transformChanged = true; transformChanged = true;
@ -363,86 +409,110 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
float scaleX = r.RequestData["scaleX"]; float scaleX = r.RequestData["scaleX"];
float finalWidth = scaleX * sourceWidth; float finalWidth = scaleX * sourceWidth;
if (!(finalWidth > -90001.0 && finalWidth < 90001.0)) if (!(finalWidth > -90001.0 && finalWidth < 90001.0))
return RequestResult::Error(RequestStatus::RequestFieldOutOfRange, "The field scaleX is too small or large for the current source resolution."); return RequestResult::Error(
RequestStatus::RequestFieldOutOfRange,
"The field scaleX is too small or large for the current source resolution.");
sceneItemTransform.scale.x = scaleX; sceneItemTransform.scale.x = scaleX;
transformChanged = true; transformChanged = true;
} }
if (r.Contains("scaleY")) { if (r.Contains("scaleY")) {
if (!r.ValidateOptionalNumber("scaleY", statusCode, comment, -90001.0, 90001.0)) if (!r.ValidateOptionalNumber("scaleY", statusCode, comment,
-90001.0, 90001.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
float scaleY = r.RequestData["scaleY"]; float scaleY = r.RequestData["scaleY"];
float finalHeight = scaleY * sourceHeight; float finalHeight = scaleY * sourceHeight;
if (!(finalHeight > -90001.0 && finalHeight < 90001.0)) if (!(finalHeight > -90001.0 && finalHeight < 90001.0))
return RequestResult::Error(RequestStatus::RequestFieldOutOfRange, "The field scaleY is too small or large for the current source resolution."); return RequestResult::Error(
RequestStatus::RequestFieldOutOfRange,
"The field scaleY is too small or large for the current source resolution.");
sceneItemTransform.scale.y = scaleY; sceneItemTransform.scale.y = scaleY;
transformChanged = true; transformChanged = true;
} }
if (r.Contains("alignment")) { if (r.Contains("alignment")) {
if (!r.ValidateOptionalNumber("alignment", statusCode, comment, 0, std::numeric_limits<uint32_t>::max())) if (!r.ValidateOptionalNumber(
"alignment", statusCode, comment, 0,
std::numeric_limits<uint32_t>::max()))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemTransform.alignment = r.RequestData["alignment"]; sceneItemTransform.alignment = r.RequestData["alignment"];
transformChanged = true; transformChanged = true;
} }
if (r.Contains("boundsType")) { if (r.Contains("boundsType")) {
if (!r.ValidateOptionalString("boundsType", statusCode, comment)) if (!r.ValidateOptionalString("boundsType", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string boundsTypeString = r.RequestData["boundsType"]; std::string boundsTypeString = r.RequestData["boundsType"];
enum obs_bounds_type boundsType = Utils::Obs::EnumHelper::GetSceneItemBoundsType(boundsTypeString); enum obs_bounds_type boundsType =
if (boundsType == OBS_BOUNDS_NONE && boundsTypeString != "OBS_BOUNDS_NONE") Utils::Obs::EnumHelper::GetSceneItemBoundsType(
return RequestResult::Error(RequestStatus::InvalidRequestField, "The field boundsType has an invalid value."); boundsTypeString);
if (boundsType == OBS_BOUNDS_NONE &&
boundsTypeString != "OBS_BOUNDS_NONE")
return RequestResult::Error(
RequestStatus::InvalidRequestField,
"The field boundsType has an invalid value.");
sceneItemTransform.bounds_type = boundsType; sceneItemTransform.bounds_type = boundsType;
transformChanged = true; transformChanged = true;
} }
if (r.Contains("boundsAlignment")) { if (r.Contains("boundsAlignment")) {
if (!r.ValidateOptionalNumber("boundsAlignment", statusCode, comment, 0, std::numeric_limits<uint32_t>::max())) if (!r.ValidateOptionalNumber(
"boundsAlignment", statusCode, comment, 0,
std::numeric_limits<uint32_t>::max()))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemTransform.bounds_alignment = r.RequestData["boundsAlignment"]; sceneItemTransform.bounds_alignment =
r.RequestData["boundsAlignment"];
transformChanged = true; transformChanged = true;
} }
if (r.Contains("boundsWidth")) { if (r.Contains("boundsWidth")) {
if (!r.ValidateOptionalNumber("boundsWidth", statusCode, comment, 1.0, 90001.0)) if (!r.ValidateOptionalNumber("boundsWidth", statusCode,
comment, 1.0, 90001.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemTransform.bounds.x = r.RequestData["boundsWidth"]; sceneItemTransform.bounds.x = r.RequestData["boundsWidth"];
transformChanged = true; transformChanged = true;
} }
if (r.Contains("boundsHeight")) { if (r.Contains("boundsHeight")) {
if (!r.ValidateOptionalNumber("boundsHeight", statusCode, comment, 1.0, 90001.0)) if (!r.ValidateOptionalNumber("boundsHeight", statusCode,
comment, 1.0, 90001.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemTransform.bounds.y = r.RequestData["boundsHeight"]; sceneItemTransform.bounds.y = r.RequestData["boundsHeight"];
transformChanged = true; transformChanged = true;
} }
if (r.Contains("cropLeft")) { if (r.Contains("cropLeft")) {
if (!r.ValidateOptionalNumber("cropLeft", statusCode, comment, 0.0, 100000.0)) if (!r.ValidateOptionalNumber("cropLeft", statusCode, comment,
0.0, 100000.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemCrop.left = r.RequestData["cropLeft"]; sceneItemCrop.left = r.RequestData["cropLeft"];
cropChanged = true; cropChanged = true;
} }
if (r.Contains("cropRight")) { if (r.Contains("cropRight")) {
if (!r.ValidateOptionalNumber("cropRight", statusCode, comment, 0.0, 100000.0)) if (!r.ValidateOptionalNumber("cropRight", statusCode, comment,
0.0, 100000.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemCrop.right = r.RequestData["cropRight"]; sceneItemCrop.right = r.RequestData["cropRight"];
cropChanged = true; cropChanged = true;
} }
if (r.Contains("cropTop")) { if (r.Contains("cropTop")) {
if (!r.ValidateOptionalNumber("cropTop", statusCode, comment, 0.0, 100000.0)) if (!r.ValidateOptionalNumber("cropTop", statusCode, comment,
0.0, 100000.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemCrop.top = r.RequestData["cropTop"]; sceneItemCrop.top = r.RequestData["cropTop"];
cropChanged = true; cropChanged = true;
} }
if (r.Contains("cropBottom")) { if (r.Contains("cropBottom")) {
if (!r.ValidateOptionalNumber("cropBottom", statusCode, comment, 0.0, 100000.0)) if (!r.ValidateOptionalNumber("cropBottom", statusCode, comment,
0.0, 100000.0))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
sceneItemCrop.bottom = r.RequestData["cropBottom"]; sceneItemCrop.bottom = r.RequestData["cropBottom"];
cropChanged = true; cropChanged = true;
} }
if (!transformChanged && !cropChanged) if (!transformChanged && !cropChanged)
return RequestResult::Error(RequestStatus::CannotAct, "You have not provided any valid transform changes."); return RequestResult::Error(
RequestStatus::CannotAct,
"You have not provided any valid transform changes.");
if (transformChanged) if (transformChanged)
obs_sceneitem_set_info(sceneItem, &sceneItemTransform); obs_sceneitem_set_info(sceneItem, &sceneItemTransform);
@ -470,11 +540,13 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemEnabled(const Request& request) RequestResult RequestHandler::GetSceneItemEnabled(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
"sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -500,12 +572,15 @@ RequestResult RequestHandler::GetSceneItemEnabled(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemEnabled(const Request& request) RequestResult RequestHandler::SetSceneItemEnabled(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
if (!(sceneItem && request.ValidateBoolean("sceneItemEnabled", statusCode, comment))) "sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem &&
request.ValidateBoolean("sceneItemEnabled", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
bool sceneItemEnabled = request.RequestData["sceneItemEnabled"]; bool sceneItemEnabled = request.RequestData["sceneItemEnabled"];
@ -532,11 +607,13 @@ RequestResult RequestHandler::SetSceneItemEnabled(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemLocked(const Request& request) RequestResult RequestHandler::GetSceneItemLocked(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
"sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -562,12 +639,15 @@ RequestResult RequestHandler::GetSceneItemLocked(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemLocked(const Request& request) RequestResult RequestHandler::SetSceneItemLocked(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
if (!(sceneItem && request.ValidateBoolean("sceneItemLocked", statusCode, comment))) "sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem &&
request.ValidateBoolean("sceneItemLocked", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
bool sceneItemLocked = request.RequestData["sceneItemLocked"]; bool sceneItemLocked = request.RequestData["sceneItemLocked"];
@ -596,16 +676,19 @@ RequestResult RequestHandler::SetSceneItemLocked(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemIndex(const Request& request) RequestResult RequestHandler::GetSceneItemIndex(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
"sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem); responseData["sceneItemIndex"] =
obs_sceneitem_get_order_position(sceneItem);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -626,12 +709,15 @@ RequestResult RequestHandler::GetSceneItemIndex(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemIndex(const Request& request) RequestResult RequestHandler::SetSceneItemIndex(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
if (!(sceneItem && request.ValidateNumber("sceneItemIndex", statusCode, comment, 0, 8192))) "sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateNumber("sceneItemIndex", statusCode,
comment, 0, 8192)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
int sceneItemIndex = request.RequestData["sceneItemIndex"]; int sceneItemIndex = request.RequestData["sceneItemIndex"];
@ -668,18 +754,21 @@ RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request) RequestResult RequestHandler::GetSceneItemBlendMode(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
"sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
auto blendMode = obs_sceneitem_get_blending_mode(sceneItem); auto blendMode = obs_sceneitem_get_blending_mode(sceneItem);
json responseData; json responseData;
responseData["sceneItemBlendMode"] = Utils::Obs::StringHelper::GetSceneItemBlendMode(blendMode); responseData["sceneItemBlendMode"] =
Utils::Obs::StringHelper::GetSceneItemBlendMode(blendMode);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -700,19 +789,26 @@ RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request)
* @api requests * @api requests
* @category scene items * @category scene items
*/ */
RequestResult RequestHandler::SetSceneItemBlendMode(const Request& request) RequestResult RequestHandler::SetSceneItemBlendMode(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
if (!(sceneItem && request.ValidateString("sceneItemBlendMode", statusCode, comment))) "sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateString("sceneItemBlendMode",
statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string blendModeString = request.RequestData["sceneItemBlendMode"]; std::string blendModeString = request.RequestData["sceneItemBlendMode"];
auto blendMode = Utils::Obs::EnumHelper::GetSceneItemBlendMode(blendModeString); auto blendMode =
if (blendMode == OBS_BLEND_NORMAL && blendModeString != "OBS_BLEND_NORMAL") Utils::Obs::EnumHelper::GetSceneItemBlendMode(blendModeString);
return RequestResult::Error(RequestStatus::InvalidRequestField, "The field sceneItemBlendMode has an invalid value."); if (blendMode == OBS_BLEND_NORMAL &&
blendModeString != "OBS_BLEND_NORMAL")
return RequestResult::Error(
RequestStatus::InvalidRequestField,
"The field sceneItemBlendMode has an invalid value.");
obs_sceneitem_set_blending_mode(sceneItem, blendMode); obs_sceneitem_set_blending_mode(sceneItem, blendMode);
@ -720,34 +816,45 @@ RequestResult RequestHandler::SetSceneItemBlendMode(const Request& request)
} }
// Intentionally undocumented // Intentionally undocumented
RequestResult RequestHandler::GetSceneItemPrivateSettings(const Request& request) RequestResult
RequestHandler::GetSceneItemPrivateSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
"sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_sceneitem_get_private_settings(sceneItem); OBSDataAutoRelease privateSettings =
obs_sceneitem_get_private_settings(sceneItem);
json responseData; json responseData;
responseData["sceneItemSettings"] = Utils::Json::ObsDataToJson(privateSettings); responseData["sceneItemSettings"] =
Utils::Json::ObsDataToJson(privateSettings);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
// Intentionally undocumented // Intentionally undocumented
RequestResult RequestHandler::SetSceneItemPrivateSettings(const Request& request) RequestResult
RequestHandler::SetSceneItemPrivateSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem(
if (!sceneItem || !request.ValidateObject("sceneItemSettings", statusCode, comment)) "sceneName", "sceneItemId", statusCode, comment,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem ||
!request.ValidateObject("sceneItemSettings", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_sceneitem_get_private_settings(sceneItem); OBSDataAutoRelease privateSettings =
obs_sceneitem_get_private_settings(sceneItem);
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["sceneItemSettings"]); OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(
request.RequestData["sceneItemSettings"]);
// Always overlays to prevent destroying internal source unintentionally // Always overlays to prevent destroying internal source unintentionally
obs_data_apply(privateSettings, newSettings); obs_data_apply(privateSettings, newSettings);

View File

@ -33,19 +33,23 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetSceneList(const Request&) RequestResult RequestHandler::GetSceneList(const Request &)
{ {
json responseData; json responseData;
OBSSourceAutoRelease currentProgramScene = obs_frontend_get_current_scene(); OBSSourceAutoRelease currentProgramScene =
obs_frontend_get_current_scene();
if (currentProgramScene) if (currentProgramScene)
responseData["currentProgramSceneName"] = obs_source_get_name(currentProgramScene); responseData["currentProgramSceneName"] =
obs_source_get_name(currentProgramScene);
else else
responseData["currentProgramSceneName"] = nullptr; responseData["currentProgramSceneName"] = nullptr;
OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene(); OBSSourceAutoRelease currentPreviewScene =
obs_frontend_get_current_preview_scene();
if (currentPreviewScene) if (currentPreviewScene)
responseData["currentPreviewSceneName"] = obs_source_get_name(currentPreviewScene); responseData["currentPreviewSceneName"] =
obs_source_get_name(currentPreviewScene);
else else
responseData["currentPreviewSceneName"] = nullptr; responseData["currentPreviewSceneName"] = nullptr;
@ -68,7 +72,7 @@ RequestResult RequestHandler::GetSceneList(const Request&)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetGroupList(const Request&) RequestResult RequestHandler::GetGroupList(const Request &)
{ {
json responseData; json responseData;
@ -89,11 +93,13 @@ RequestResult RequestHandler::GetGroupList(const Request&)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetCurrentProgramScene(const Request&) RequestResult RequestHandler::GetCurrentProgramScene(const Request &)
{ {
json responseData; json responseData;
OBSSourceAutoRelease currentProgramScene = obs_frontend_get_current_scene(); OBSSourceAutoRelease currentProgramScene =
responseData["currentProgramSceneName"] = obs_source_get_name(currentProgramScene); obs_frontend_get_current_scene();
responseData["currentProgramSceneName"] =
obs_source_get_name(currentProgramScene);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -110,11 +116,12 @@ RequestResult RequestHandler::GetCurrentProgramScene(const Request&)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::SetCurrentProgramScene(const Request& request) RequestResult RequestHandler::SetCurrentProgramScene(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease scene =
request.ValidateScene("sceneName", statusCode, comment);
if (!scene) if (!scene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -137,15 +144,17 @@ RequestResult RequestHandler::SetCurrentProgramScene(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetCurrentPreviewScene(const Request&) RequestResult RequestHandler::GetCurrentPreviewScene(const Request &)
{ {
if (!obs_frontend_preview_program_mode_active()) if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive); return RequestResult::Error(RequestStatus::StudioModeNotActive);
OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene(); OBSSourceAutoRelease currentPreviewScene =
obs_frontend_get_current_preview_scene();
json responseData; json responseData;
responseData["currentPreviewSceneName"] = obs_source_get_name(currentPreviewScene); responseData["currentPreviewSceneName"] =
obs_source_get_name(currentPreviewScene);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -164,14 +173,15 @@ RequestResult RequestHandler::GetCurrentPreviewScene(const Request&)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::SetCurrentPreviewScene(const Request& request) RequestResult RequestHandler::SetCurrentPreviewScene(const Request &request)
{ {
if (!obs_frontend_preview_program_mode_active()) if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive); return RequestResult::Error(RequestStatus::StudioModeNotActive);
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease scene =
request.ValidateScene("sceneName", statusCode, comment);
if (!scene) if (!scene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -192,7 +202,7 @@ RequestResult RequestHandler::SetCurrentPreviewScene(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::CreateScene(const Request& request) RequestResult RequestHandler::CreateScene(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -203,11 +213,15 @@ RequestResult RequestHandler::CreateScene(const Request& request)
OBSSourceAutoRelease scene = obs_get_source_by_name(sceneName.c_str()); OBSSourceAutoRelease scene = obs_get_source_by_name(sceneName.c_str());
if (scene) if (scene)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that scene name."); return RequestResult::Error(
RequestStatus::ResourceAlreadyExists,
"A source already exists by that scene name.");
obs_scene_t *createdScene = obs_scene_create(sceneName.c_str()); obs_scene_t *createdScene = obs_scene_create(sceneName.c_str());
if (!createdScene) if (!createdScene)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Failed to create the scene."); return RequestResult::Error(
RequestStatus::ResourceCreationFailed,
"Failed to create the scene.");
obs_scene_release(createdScene); obs_scene_release(createdScene);
@ -226,16 +240,19 @@ RequestResult RequestHandler::CreateScene(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::RemoveScene(const Request& request) RequestResult RequestHandler::RemoveScene(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease scene =
request.ValidateScene("sceneName", statusCode, comment);
if (!scene) if (!scene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (Utils::Obs::NumberHelper::GetSceneCount() < 2) if (Utils::Obs::NumberHelper::GetSceneCount() < 2)
return RequestResult::Error(RequestStatus::NotEnoughResources, "You cannot remove the last scene in the collection."); return RequestResult::Error(
RequestStatus::NotEnoughResources,
"You cannot remove the last scene in the collection.");
obs_source_remove(scene); obs_source_remove(scene);
@ -255,19 +272,24 @@ RequestResult RequestHandler::RemoveScene(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::SetSceneName(const Request& request) RequestResult RequestHandler::SetSceneName(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease scene =
if (!(scene && request.ValidateString("newSceneName", statusCode, comment))) request.ValidateScene("sceneName", statusCode, comment);
if (!(scene &&
request.ValidateString("newSceneName", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
std::string newSceneName = request.RequestData["newSceneName"]; std::string newSceneName = request.RequestData["newSceneName"];
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newSceneName.c_str()); OBSSourceAutoRelease existingSource =
obs_get_source_by_name(newSceneName.c_str());
if (existingSource) if (existingSource)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that new scene name."); return RequestResult::Error(
RequestStatus::ResourceAlreadyExists,
"A source already exists by that new scene name.");
obs_source_set_name(scene, newSceneName.c_str()); obs_source_set_name(scene, newSceneName.c_str());
@ -289,25 +311,30 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request& request) RequestResult
RequestHandler::GetSceneSceneTransitionOverride(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease scene =
request.ValidateScene("sceneName", statusCode, comment);
if (!scene) if (!scene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(scene); OBSDataAutoRelease privateSettings =
obs_source_get_private_settings(scene);
json responseData; json responseData;
const char *transitionName = obs_data_get_string(privateSettings, "transition"); const char *transitionName =
obs_data_get_string(privateSettings, "transition");
if (transitionName && strlen(transitionName)) if (transitionName && strlen(transitionName))
responseData["transitionName"] = transitionName; responseData["transitionName"] = transitionName;
else else
responseData["transitionName"] = nullptr; responseData["transitionName"] = nullptr;
if (obs_data_has_user_value(privateSettings, "transition_duration")) if (obs_data_has_user_value(privateSettings, "transition_duration"))
responseData["transitionDuration"] = obs_data_get_int(privateSettings, "transition_duration"); responseData["transitionDuration"] = obs_data_get_int(
privateSettings, "transition_duration");
else else
responseData["transitionDuration"] = nullptr; responseData["transitionDuration"] = nullptr;
@ -328,40 +355,55 @@ RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request& req
* @api requests * @api requests
* @category scenes * @category scenes
*/ */
RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& request) RequestResult
RequestHandler::SetSceneSceneTransitionOverride(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); OBSSourceAutoRelease scene =
request.ValidateScene("sceneName", statusCode, comment);
if (!scene) if (!scene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(scene); OBSDataAutoRelease privateSettings =
obs_source_get_private_settings(scene);
bool hasName = request.RequestData.contains("transitionName"); bool hasName = request.RequestData.contains("transitionName");
if (hasName && !request.RequestData["transitionName"].is_null()) { if (hasName && !request.RequestData["transitionName"].is_null()) {
if (!request.ValidateOptionalString("transitionName", statusCode, comment)) if (!request.ValidateOptionalString("transitionName",
statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSSourceAutoRelease transition = Utils::Obs::SearchHelper::GetSceneTransitionByName(request.RequestData["transitionName"]); OBSSourceAutoRelease transition =
Utils::Obs::SearchHelper::GetSceneTransitionByName(
request.RequestData["transitionName"]);
if (!transition) if (!transition)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene transition was found by that name."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"No scene transition was found by that name.");
} }
bool hasDuration = request.RequestData.contains("transitionDuration"); bool hasDuration = request.RequestData.contains("transitionDuration");
if (hasDuration && !request.RequestData["transitionDuration"].is_null()) { if (hasDuration &&
if (!request.ValidateOptionalNumber("transitionDuration", statusCode, comment, 50, 20000)) !request.RequestData["transitionDuration"].is_null()) {
if (!request.ValidateOptionalNumber("transitionDuration",
statusCode, comment, 50,
20000))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
} }
if (!hasName && !hasDuration) if (!hasName && !hasDuration)
return RequestResult::Error(RequestStatus::MissingRequestField, "Your request data must include either `transitionName` or `transitionDuration`."); return RequestResult::Error(
RequestStatus::MissingRequestField,
"Your request data must include either `transitionName` or `transitionDuration`.");
if (hasName) { if (hasName) {
if (request.RequestData["transitionName"].is_null()) { if (request.RequestData["transitionName"].is_null()) {
obs_data_erase(privateSettings, "transition"); obs_data_erase(privateSettings, "transition");
} else { } else {
std::string transitionName = request.RequestData["transitionName"]; std::string transitionName =
obs_data_set_string(privateSettings, "transition", transitionName.c_str()); request.RequestData["transitionName"];
obs_data_set_string(privateSettings, "transition",
transitionName.c_str());
} }
} }
@ -369,7 +411,9 @@ RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& req
if (request.RequestData["transitionDuration"].is_null()) { if (request.RequestData["transitionDuration"].is_null()) {
obs_data_erase(privateSettings, "transition_duration"); obs_data_erase(privateSettings, "transition_duration");
} else { } else {
obs_data_set_int(privateSettings, "transition_duration", request.RequestData["transitionDuration"]); obs_data_set_int(
privateSettings, "transition_duration",
request.RequestData["transitionDuration"]);
} }
} }

View File

@ -25,12 +25,15 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestHandler.h" #include "RequestHandler.h"
QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t requestedWidth = 0, uint32_t requestedHeight = 0) QImage TakeSourceScreenshot(obs_source_t *source, bool &success,
uint32_t requestedWidth = 0,
uint32_t requestedHeight = 0)
{ {
// Get info about the requested source // Get info about the requested source
const uint32_t sourceWidth = obs_source_get_base_width(source); const uint32_t sourceWidth = obs_source_get_base_width(source);
const uint32_t sourceHeight = obs_source_get_base_height(source); const uint32_t sourceHeight = obs_source_get_base_height(source);
const double sourceAspectRatio = ((double)sourceWidth / (double)sourceHeight); const double sourceAspectRatio =
((double)sourceWidth / (double)sourceHeight);
uint32_t imgWidth = sourceWidth; uint32_t imgWidth = sourceWidth;
uint32_t imgHeight = sourceHeight; uint32_t imgHeight = sourceHeight;
@ -56,14 +59,15 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques
ret.fill(0); ret.fill(0);
// Video image buffer // Video image buffer
uint8_t* videoData = nullptr; uint8_t *videoData = nullptr;
uint32_t videoLinesize = 0; uint32_t videoLinesize = 0;
// Enter graphics context // Enter graphics context
obs_enter_graphics(); obs_enter_graphics();
gs_texrender_t* texRender = gs_texrender_create(GS_RGBA, GS_ZS_NONE); gs_texrender_t *texRender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
gs_stagesurf_t* stageSurface = gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA); gs_stagesurf_t *stageSurface =
gs_stagesurface_create(imgWidth, imgHeight, GS_RGBA);
success = false; success = false;
gs_texrender_reset(texRender); gs_texrender_reset(texRender);
@ -72,7 +76,8 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques
vec4_zero(&background); vec4_zero(&background);
gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0); gs_clear(GS_CLEAR_COLOR, &background, 0.0f, 0);
gs_ortho(0.0f, (float)sourceWidth, 0.0f, (float)sourceHeight, -100.0f, 100.0f); gs_ortho(0.0f, (float)sourceWidth, 0.0f, (float)sourceHeight,
-100.0f, 100.0f);
gs_blend_state_push(); gs_blend_state_push();
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO); gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
@ -84,11 +89,15 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques
gs_blend_state_pop(); gs_blend_state_pop();
gs_texrender_end(texRender); gs_texrender_end(texRender);
gs_stage_texture(stageSurface, gs_texrender_get_texture(texRender)); gs_stage_texture(stageSurface,
if (gs_stagesurface_map(stageSurface, &videoData, &videoLinesize)) { gs_texrender_get_texture(texRender));
if (gs_stagesurface_map(stageSurface, &videoData,
&videoLinesize)) {
int lineSize = ret.bytesPerLine(); int lineSize = ret.bytesPerLine();
for (uint y = 0; y < imgHeight; y++) { for (uint y = 0; y < imgHeight; y++) {
memcpy(ret.scanLine(y), videoData + (y * videoLinesize), lineSize); memcpy(ret.scanLine(y),
videoData + (y * videoLinesize),
lineSize);
} }
gs_stagesurface_unmap(stageSurface); gs_stagesurface_unmap(stageSurface);
success = true; success = true;
@ -126,16 +135,20 @@ bool IsImageFormatValid(std::string format)
* @api requests * @api requests
* @category sources * @category sources
*/ */
RequestResult RequestHandler::GetSourceActive(const Request& request) RequestResult RequestHandler::GetSourceActive(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source =
request.ValidateSource("sourceName", statusCode, comment);
if (!source) if (!source)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT &&
return RequestResult::Error(RequestStatus::InvalidResourceType, "The specified source is not an input or a scene."); obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE)
return RequestResult::Error(
RequestStatus::InvalidResourceType,
"The specified source is not an input or a scene.");
json responseData; json responseData;
responseData["videoActive"] = obs_source_active(source); responseData["videoActive"] = obs_source_active(source);
@ -166,63 +179,83 @@ RequestResult RequestHandler::GetSourceActive(const Request& request)
* @api requests * @api requests
* @category sources * @category sources
*/ */
RequestResult RequestHandler::GetSourceScreenshot(const Request& request) RequestResult RequestHandler::GetSourceScreenshot(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source =
if (!(source && request.ValidateString("imageFormat", statusCode, comment))) request.ValidateSource("sourceName", statusCode, comment);
if (!(source &&
request.ValidateString("imageFormat", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT &&
return RequestResult::Error(RequestStatus::InvalidResourceType, "The specified source is not an input or a scene."); obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE)
return RequestResult::Error(
RequestStatus::InvalidResourceType,
"The specified source is not an input or a scene.");
std::string imageFormat = request.RequestData["imageFormat"]; std::string imageFormat = request.RequestData["imageFormat"];
if (!IsImageFormatValid(imageFormat)) if (!IsImageFormatValid(imageFormat))
return RequestResult::Error(RequestStatus::InvalidRequestField, "Your specified image format is invalid or not supported by this system."); return RequestResult::Error(
RequestStatus::InvalidRequestField,
"Your specified image format is invalid or not supported by this system.");
uint32_t requestedWidth{0}; uint32_t requestedWidth{0};
uint32_t requestedHeight{0}; uint32_t requestedHeight{0};
int compressionQuality{-1}; int compressionQuality{-1};
if (request.Contains("imageWidth")) { if (request.Contains("imageWidth")) {
if (!request.ValidateOptionalNumber("imageWidth", statusCode, comment, 8, 4096)) if (!request.ValidateOptionalNumber("imageWidth", statusCode,
comment, 8, 4096))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
requestedWidth = request.RequestData["imageWidth"]; requestedWidth = request.RequestData["imageWidth"];
} }
if (request.Contains("imageHeight")) { if (request.Contains("imageHeight")) {
if (!request.ValidateOptionalNumber("imageHeight", statusCode, comment, 8, 4096)) if (!request.ValidateOptionalNumber("imageHeight", statusCode,
comment, 8, 4096))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
requestedHeight = request.RequestData["imageHeight"]; requestedHeight = request.RequestData["imageHeight"];
} }
if (request.Contains("imageCompressionQuality")) { if (request.Contains("imageCompressionQuality")) {
if (!request.ValidateOptionalNumber("imageCompressionQuality", statusCode, comment, -1, 100)) if (!request.ValidateOptionalNumber("imageCompressionQuality",
statusCode, comment, -1,
100))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
compressionQuality = request.RequestData["imageCompressionQuality"]; compressionQuality =
request.RequestData["imageCompressionQuality"];
} }
bool success; bool success;
QImage renderedImage = TakeSourceScreenshot(source, success, requestedWidth, requestedHeight); QImage renderedImage = TakeSourceScreenshot(
source, success, requestedWidth, requestedHeight);
if (!success) if (!success)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to render screenshot."); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Failed to render screenshot.");
QByteArray encodedImgBytes; QByteArray encodedImgBytes;
QBuffer buffer(&encodedImgBytes); QBuffer buffer(&encodedImgBytes);
buffer.open(QBuffer::WriteOnly); buffer.open(QBuffer::WriteOnly);
if (!renderedImage.save(&buffer, imageFormat.c_str(), compressionQuality)) if (!renderedImage.save(&buffer, imageFormat.c_str(),
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to encode screenshot."); compressionQuality))
return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Failed to encode screenshot.");
buffer.close(); buffer.close();
QString encodedPicture = QString("data:image/%1;base64,").arg(imageFormat.c_str()).append(encodedImgBytes.toBase64()); QString encodedPicture = QString("data:image/%1;base64,")
.arg(imageFormat.c_str())
.append(encodedImgBytes.toBase64());
json responseData; json responseData;
responseData["imageData"] = encodedPicture.toStdString(); responseData["imageData"] = encodedPicture.toStdString();
@ -253,95 +286,123 @@ RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
* @api requests * @api requests
* @category sources * @category sources
*/ */
RequestResult RequestHandler::SaveSourceScreenshot(const Request& request) RequestResult RequestHandler::SaveSourceScreenshot(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source =
if (!(source && request.ValidateString("imageFormat", statusCode, comment) && request.ValidateString("imageFilePath", statusCode, comment))) request.ValidateSource("sourceName", statusCode, comment);
if (!(source &&
request.ValidateString("imageFormat", statusCode, comment) &&
request.ValidateString("imageFilePath", statusCode, comment)))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT &&
return RequestResult::Error(RequestStatus::InvalidResourceType, "The specified source is not an input or a scene."); obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE)
return RequestResult::Error(
RequestStatus::InvalidResourceType,
"The specified source is not an input or a scene.");
std::string imageFormat = request.RequestData["imageFormat"]; std::string imageFormat = request.RequestData["imageFormat"];
std::string imageFilePath = request.RequestData["imageFilePath"]; std::string imageFilePath = request.RequestData["imageFilePath"];
if (!IsImageFormatValid(imageFormat)) if (!IsImageFormatValid(imageFormat))
return RequestResult::Error(RequestStatus::InvalidRequestField, "Your specified image format is invalid or not supported by this system."); return RequestResult::Error(
RequestStatus::InvalidRequestField,
"Your specified image format is invalid or not supported by this system.");
QFileInfo filePathInfo(QString::fromStdString(imageFilePath)); QFileInfo filePathInfo(QString::fromStdString(imageFilePath));
if (!filePathInfo.absoluteDir().exists()) if (!filePathInfo.absoluteDir().exists())
return RequestResult::Error(RequestStatus::ResourceNotFound, "The directory for your file path does not exist."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"The directory for your file path does not exist.");
uint32_t requestedWidth{0}; uint32_t requestedWidth{0};
uint32_t requestedHeight{0}; uint32_t requestedHeight{0};
int compressionQuality{-1}; int compressionQuality{-1};
if (request.Contains("imageWidth")) { if (request.Contains("imageWidth")) {
if (!request.ValidateOptionalNumber("imageWidth", statusCode, comment, 8, 4096)) if (!request.ValidateOptionalNumber("imageWidth", statusCode,
comment, 8, 4096))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
requestedWidth = request.RequestData["imageWidth"]; requestedWidth = request.RequestData["imageWidth"];
} }
if (request.Contains("imageHeight")) { if (request.Contains("imageHeight")) {
if (!request.ValidateOptionalNumber("imageHeight", statusCode, comment, 8, 4096)) if (!request.ValidateOptionalNumber("imageHeight", statusCode,
comment, 8, 4096))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
requestedHeight = request.RequestData["imageHeight"]; requestedHeight = request.RequestData["imageHeight"];
} }
if (request.Contains("imageCompressionQuality")) { if (request.Contains("imageCompressionQuality")) {
if (!request.ValidateOptionalNumber("imageCompressionQuality", statusCode, comment, -1, 100)) if (!request.ValidateOptionalNumber("imageCompressionQuality",
statusCode, comment, -1,
100))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
compressionQuality = request.RequestData["imageCompressionQuality"]; compressionQuality =
request.RequestData["imageCompressionQuality"];
} }
bool success; bool success;
QImage renderedImage = TakeSourceScreenshot(source, success, requestedWidth, requestedHeight); QImage renderedImage = TakeSourceScreenshot(
source, success, requestedWidth, requestedHeight);
if (!success) if (!success)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to render screenshot."); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Failed to render screenshot.");
QString absoluteFilePath = filePathInfo.absoluteFilePath(); QString absoluteFilePath = filePathInfo.absoluteFilePath();
if (!renderedImage.save(absoluteFilePath, imageFormat.c_str(), compressionQuality)) if (!renderedImage.save(absoluteFilePath, imageFormat.c_str(),
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to save screenshot."); compressionQuality))
return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"Failed to save screenshot.");
return RequestResult::Success(); return RequestResult::Success();
} }
// Intentionally undocumented // Intentionally undocumented
RequestResult RequestHandler::GetSourcePrivateSettings(const Request& request) RequestResult RequestHandler::GetSourcePrivateSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source =
request.ValidateSource("sourceName", statusCode, comment);
if (!source) if (!source)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source); OBSDataAutoRelease privateSettings =
obs_source_get_private_settings(source);
json responseData; json responseData;
responseData["sourceSettings"] = Utils::Json::ObsDataToJson(privateSettings); responseData["sourceSettings"] =
Utils::Json::ObsDataToJson(privateSettings);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
// Intentionally undocumented // Intentionally undocumented
RequestResult RequestHandler::SetSourcePrivateSettings(const Request& request) RequestResult RequestHandler::SetSourcePrivateSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); OBSSourceAutoRelease source =
if (!source || !request.ValidateObject("sourceSettings", statusCode, comment)) request.ValidateSource("sourceName", statusCode, comment);
if (!source ||
!request.ValidateObject("sourceSettings", statusCode, comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source); OBSDataAutoRelease privateSettings =
obs_source_get_private_settings(source);
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["sourceSettings"]); OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(
request.RequestData["sourceSettings"]);
// Always overlays to prevent destroying internal source data unintentionally // Always overlays to prevent destroying internal source data unintentionally
obs_data_apply(privateSettings, newSettings); obs_data_apply(privateSettings, newSettings);

View File

@ -37,20 +37,26 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category stream * @category stream
*/ */
RequestResult RequestHandler::GetStreamStatus(const Request&) RequestResult RequestHandler::GetStreamStatus(const Request &)
{ {
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output(); OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(streamOutput); uint64_t outputDuration =
Utils::Obs::NumberHelper::GetOutputDuration(streamOutput);
json responseData; json responseData;
responseData["outputActive"] = obs_output_active(streamOutput); responseData["outputActive"] = obs_output_active(streamOutput);
responseData["outputReconnecting"] = obs_output_reconnecting(streamOutput); responseData["outputReconnecting"] =
responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration); obs_output_reconnecting(streamOutput);
responseData["outputTimecode"] =
Utils::Obs::StringHelper::DurationToTimecode(outputDuration);
responseData["outputDuration"] = outputDuration; responseData["outputDuration"] = outputDuration;
responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(streamOutput); responseData["outputBytes"] =
responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(streamOutput); (uint64_t)obs_output_get_total_bytes(streamOutput);
responseData["outputTotalFrames"] = obs_output_get_total_frames(streamOutput); responseData["outputSkippedFrames"] =
obs_output_get_frames_dropped(streamOutput);
responseData["outputTotalFrames"] =
obs_output_get_total_frames(streamOutput);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -67,7 +73,7 @@ RequestResult RequestHandler::GetStreamStatus(const Request&)
* @api requests * @api requests
* @category stream * @category stream
*/ */
RequestResult RequestHandler::ToggleStream(const Request&) RequestResult RequestHandler::ToggleStream(const Request &)
{ {
json responseData; json responseData;
if (obs_frontend_streaming_active()) { if (obs_frontend_streaming_active()) {
@ -91,7 +97,7 @@ RequestResult RequestHandler::ToggleStream(const Request&)
* @api requests * @api requests
* @category stream * @category stream
*/ */
RequestResult RequestHandler::StartStream(const Request&) RequestResult RequestHandler::StartStream(const Request &)
{ {
if (obs_frontend_streaming_active()) if (obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::OutputRunning); return RequestResult::Error(RequestStatus::OutputRunning);
@ -112,7 +118,7 @@ RequestResult RequestHandler::StartStream(const Request&)
* @api requests * @api requests
* @category stream * @category stream
*/ */
RequestResult RequestHandler::StopStream(const Request&) RequestResult RequestHandler::StopStream(const Request &)
{ {
if (!obs_frontend_streaming_active()) if (!obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::OutputNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
@ -135,7 +141,7 @@ RequestResult RequestHandler::StopStream(const Request&)
* @category stream * @category stream
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SendStreamCaption(const Request& request) RequestResult RequestHandler::SendStreamCaption(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;

View File

@ -35,10 +35,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::GetTransitionKindList(const Request&) RequestResult RequestHandler::GetTransitionKindList(const Request &)
{ {
json responseData; json responseData;
responseData["transitionKinds"] = Utils::Obs::ArrayHelper::GetTransitionKindList(); responseData["transitionKinds"] =
Utils::Obs::ArrayHelper::GetTransitionKindList();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -56,20 +57,23 @@ RequestResult RequestHandler::GetTransitionKindList(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::GetSceneTransitionList(const Request&) RequestResult RequestHandler::GetSceneTransitionList(const Request &)
{ {
json responseData; json responseData;
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (transition) { if (transition) {
responseData["currentSceneTransitionName"] = obs_source_get_name(transition); responseData["currentSceneTransitionName"] =
responseData["currentSceneTransitionKind"] = obs_source_get_id(transition); obs_source_get_name(transition);
responseData["currentSceneTransitionKind"] =
obs_source_get_id(transition);
} else { } else {
responseData["currentSceneTransitionName"] = nullptr; responseData["currentSceneTransitionName"] = nullptr;
responseData["currentSceneTransitionKind"] = nullptr; responseData["currentSceneTransitionKind"] = nullptr;
} }
responseData["transitions"] = Utils::Obs::ArrayHelper::GetSceneTransitionList(); responseData["transitions"] =
Utils::Obs::ArrayHelper::GetSceneTransitionList();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -91,11 +95,13 @@ RequestResult RequestHandler::GetSceneTransitionList(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::GetCurrentSceneTransition(const Request&) RequestResult RequestHandler::GetCurrentSceneTransition(const Request &)
{ {
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!transition) if (!transition)
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen! return RequestResult::Error(
RequestStatus::InvalidResourceState,
"OBS does not currently have a scene transition set."); // This should not happen!
json responseData; json responseData;
responseData["transitionName"] = obs_source_get_name(transition); responseData["transitionName"] = obs_source_get_name(transition);
@ -106,13 +112,16 @@ RequestResult RequestHandler::GetCurrentSceneTransition(const Request&)
responseData["transitionDuration"] = nullptr; responseData["transitionDuration"] = nullptr;
} else { } else {
responseData["transitionFixed"] = false; responseData["transitionFixed"] = false;
responseData["transitionDuration"] = obs_frontend_get_transition_duration(); responseData["transitionDuration"] =
obs_frontend_get_transition_duration();
} }
if (obs_source_configurable(transition)) { if (obs_source_configurable(transition)) {
responseData["transitionConfigurable"] = true; responseData["transitionConfigurable"] = true;
OBSDataAutoRelease transitionSettings = obs_source_get_settings(transition); OBSDataAutoRelease transitionSettings =
responseData["transitionSettings"] = Utils::Json::ObsDataToJson(transitionSettings); obs_source_get_settings(transition);
responseData["transitionSettings"] =
Utils::Json::ObsDataToJson(transitionSettings);
} else { } else {
responseData["transitionConfigurable"] = false; responseData["transitionConfigurable"] = false;
responseData["transitionSettings"] = nullptr; responseData["transitionSettings"] = nullptr;
@ -135,7 +144,7 @@ RequestResult RequestHandler::GetCurrentSceneTransition(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::SetCurrentSceneTransition(const Request& request) RequestResult RequestHandler::SetCurrentSceneTransition(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -144,9 +153,13 @@ RequestResult RequestHandler::SetCurrentSceneTransition(const Request& request)
std::string transitionName = request.RequestData["transitionName"]; std::string transitionName = request.RequestData["transitionName"];
OBSSourceAutoRelease transition = Utils::Obs::SearchHelper::GetSceneTransitionByName(transitionName); OBSSourceAutoRelease transition =
Utils::Obs::SearchHelper::GetSceneTransitionByName(
transitionName);
if (!transition) if (!transition)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene transition was found by that name."); return RequestResult::Error(
RequestStatus::ResourceNotFound,
"No scene transition was found by that name.");
obs_frontend_set_current_transition(transition); obs_frontend_set_current_transition(transition);
@ -165,11 +178,13 @@ RequestResult RequestHandler::SetCurrentSceneTransition(const Request& request)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request& request) RequestResult
RequestHandler::SetCurrentSceneTransitionDuration(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!request.ValidateNumber("transitionDuration", statusCode, comment, 50, 20000)) if (!request.ValidateNumber("transitionDuration", statusCode, comment,
50, 20000))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
int transitionDuration = request.RequestData["transitionDuration"]; int transitionDuration = request.RequestData["transitionDuration"];
@ -192,31 +207,41 @@ RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request& r
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& request) RequestResult
RequestHandler::SetCurrentSceneTransitionSettings(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
if (!request.ValidateObject("transitionSettings", statusCode, comment, true)) if (!request.ValidateObject("transitionSettings", statusCode, comment,
true))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!transition) if (!transition)
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen! return RequestResult::Error(
RequestStatus::InvalidResourceState,
"OBS does not currently have a scene transition set."); // This should not happen!
if (!obs_source_configurable(transition)) if (!obs_source_configurable(transition))
return RequestResult::Error(RequestStatus::ResourceNotConfigurable, "The current transition does not support custom settings."); return RequestResult::Error(
RequestStatus::ResourceNotConfigurable,
"The current transition does not support custom settings.");
bool overlay = true; bool overlay = true;
if (request.Contains("overlay")) { if (request.Contains("overlay")) {
if (!request.ValidateOptionalBoolean("overlay", statusCode, comment)) if (!request.ValidateOptionalBoolean("overlay", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
overlay = request.RequestData["overlay"]; overlay = request.RequestData["overlay"];
} }
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["transitionSettings"]); OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(
request.RequestData["transitionSettings"]);
if (!newSettings) if (!newSettings)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "An internal data conversion operation failed. Please report this!"); return RequestResult::Error(
RequestStatus::RequestProcessingFailed,
"An internal data conversion operation failed. Please report this!");
if (overlay) if (overlay)
obs_source_update(transition, newSettings); obs_source_update(transition, newSettings);
@ -242,11 +267,13 @@ RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& r
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request&) RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request &)
{ {
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!transition) if (!transition)
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen! return RequestResult::Error(
RequestStatus::InvalidResourceState,
"OBS does not currently have a scene transition set."); // This should not happen!
json responseData; json responseData;
responseData["transitionCursor"] = obs_transition_get_time(transition); responseData["transitionCursor"] = obs_transition_get_time(transition);
@ -264,12 +291,13 @@ RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::TriggerStudioModeTransition(const Request&) RequestResult RequestHandler::TriggerStudioModeTransition(const Request &)
{ {
if (!obs_frontend_preview_program_mode_active()) if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive); return RequestResult::Error(RequestStatus::StudioModeNotActive);
OBSSourceAutoRelease previewScene = obs_frontend_get_current_preview_scene(); OBSSourceAutoRelease previewScene =
obs_frontend_get_current_preview_scene();
obs_frontend_set_current_scene(previewScene); obs_frontend_set_current_scene(previewScene);
@ -291,7 +319,7 @@ RequestResult RequestHandler::TriggerStudioModeTransition(const Request&)
* @api requests * @api requests
* @category transitions * @category transitions
*/ */
RequestResult RequestHandler::SetTBarPosition(const Request& request) RequestResult RequestHandler::SetTBarPosition(const Request &request)
{ {
if (!obs_frontend_preview_program_mode_active()) if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive); return RequestResult::Error(RequestStatus::StudioModeNotActive);
@ -303,13 +331,16 @@ RequestResult RequestHandler::SetTBarPosition(const Request& request)
bool release = true; bool release = true;
if (request.Contains("release")) { if (request.Contains("release")) {
if (!request.ValidateOptionalBoolean("release", statusCode, comment)) if (!request.ValidateOptionalBoolean("release", statusCode,
comment))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
} }
OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!transition) if (!transition)
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen! return RequestResult::Error(
RequestStatus::InvalidResourceState,
"OBS does not currently have a scene transition set."); // This should not happen!
float position = request.RequestData["position"]; float position = request.RequestData["position"];

View File

@ -36,10 +36,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetStudioModeEnabled(const Request&) RequestResult RequestHandler::GetStudioModeEnabled(const Request &)
{ {
json responseData; json responseData;
responseData["studioModeEnabled"] = obs_frontend_preview_program_mode_active(); responseData["studioModeEnabled"] =
obs_frontend_preview_program_mode_active();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -55,7 +56,7 @@ RequestResult RequestHandler::GetStudioModeEnabled(const Request&)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::SetStudioModeEnabled(const Request& request) RequestResult RequestHandler::SetStudioModeEnabled(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -63,14 +64,20 @@ RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
if (obs_frontend_preview_program_mode_active() != request.RequestData["studioModeEnabled"]) { if (obs_frontend_preview_program_mode_active() !=
request.RequestData["studioModeEnabled"]) {
// (Bad) Create a boolean then pass it as a reference to the task. Requires `wait` in obs_queue_task() to be true, else undefined behavior // (Bad) Create a boolean then pass it as a reference to the task. Requires `wait` in obs_queue_task() to be true, else undefined behavior
bool studioModeEnabled = request.RequestData["studioModeEnabled"]; bool studioModeEnabled =
request.RequestData["studioModeEnabled"];
// Queue the task inside of the UI thread to prevent race conditions // Queue the task inside of the UI thread to prevent race conditions
obs_queue_task(OBS_TASK_UI, [](void* param) { obs_queue_task(
auto studioModeEnabled = (bool*)param; OBS_TASK_UI,
obs_frontend_set_preview_program_mode(*studioModeEnabled); [](void *param) {
}, &studioModeEnabled, true); auto studioModeEnabled = (bool *)param;
obs_frontend_set_preview_program_mode(
*studioModeEnabled);
},
&studioModeEnabled, true);
} }
return RequestResult::Success(); return RequestResult::Success();
@ -88,11 +95,12 @@ RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::OpenInputPropertiesDialog(const Request& request) RequestResult RequestHandler::OpenInputPropertiesDialog(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -113,11 +121,12 @@ RequestResult RequestHandler::OpenInputPropertiesDialog(const Request& request)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::OpenInputFiltersDialog(const Request& request) RequestResult RequestHandler::OpenInputFiltersDialog(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -138,16 +147,19 @@ RequestResult RequestHandler::OpenInputFiltersDialog(const Request& request)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::OpenInputInteractDialog(const Request& request) RequestResult RequestHandler::OpenInputInteractDialog(const Request &request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); OBSSourceAutoRelease input =
request.ValidateInput("inputName", statusCode, comment);
if (!input) if (!input)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_INTERACTION)) if (!(obs_source_get_output_flags(input) & OBS_SOURCE_INTERACTION))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support interaction."); return RequestResult::Error(
RequestStatus::InvalidResourceState,
"The specified input does not support interaction.");
obs_frontend_open_source_interaction(input); obs_frontend_open_source_interaction(input);
@ -166,15 +178,15 @@ RequestResult RequestHandler::OpenInputInteractDialog(const Request& request)
* @category ui * @category ui
* @api requests * @api requests
*/ */
RequestResult RequestHandler::GetMonitorList(const Request&) RequestResult RequestHandler::GetMonitorList(const Request &)
{ {
json responseData; json responseData;
std::vector<json> monitorsData; std::vector<json> monitorsData;
QList<QScreen *> screensList = QGuiApplication::screens(); QList<QScreen *> screensList = QGuiApplication::screens();
for (int screenIndex = 0; screenIndex < screensList.size(); screenIndex++) for (int screenIndex = 0; screenIndex < screensList.size();
{ screenIndex++) {
json screenData; json screenData;
QScreen const* screen = screensList[screenIndex]; QScreen const *screen = screensList[screenIndex];
std::stringstream nameAndIndex; std::stringstream nameAndIndex;
nameAndIndex << screen->name().toStdString(); nameAndIndex << screen->name().toStdString();
nameAndIndex << '(' << screenIndex << ')'; nameAndIndex << '(' << screenIndex << ')';

View File

@ -29,88 +29,115 @@ json GetDefaultJsonObject(const json &requestData)
return requestData; return requestData;
} }
Request::Request(const std::string &requestType, const json &requestData, const RequestBatchExecutionType::RequestBatchExecutionType executionType) : Request::Request(const std::string &requestType, const json &requestData,
RequestType(requestType), const RequestBatchExecutionType::RequestBatchExecutionType
HasRequestData(requestData.is_object()), executionType)
RequestData(GetDefaultJsonObject(requestData)), : RequestType(requestType),
ExecutionType(executionType) HasRequestData(requestData.is_object()),
RequestData(GetDefaultJsonObject(requestData)),
ExecutionType(executionType)
{ {
} }
bool Request::Contains(const std::string &keyName) const bool Request::Contains(const std::string &keyName) const
{ {
return (RequestData.contains(keyName) && !RequestData[keyName].is_null()); return (RequestData.contains(keyName) &&
!RequestData[keyName].is_null());
} }
bool Request::ValidateBasic(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const bool Request::ValidateBasic(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const
{ {
if (!HasRequestData) { if (!HasRequestData) {
statusCode = RequestStatus::MissingRequestData; statusCode = RequestStatus::MissingRequestData;
comment = "Your request data is missing or invalid (non-object)"; comment =
"Your request data is missing or invalid (non-object)";
return false; return false;
} }
if (!RequestData.contains(keyName) || RequestData[keyName].is_null()) { if (!RequestData.contains(keyName) || RequestData[keyName].is_null()) {
statusCode = RequestStatus::MissingRequestField; statusCode = RequestStatus::MissingRequestField;
comment = std::string("Your request is missing the `") + keyName + "` field."; comment = std::string("Your request is missing the `") +
keyName + "` field.";
return false; return false;
} }
return true; return true;
} }
bool Request::ValidateOptionalNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue, const double maxValue) const bool Request::ValidateOptionalNumber(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const double minValue,
const double maxValue) const
{ {
if (!RequestData[keyName].is_number()) { if (!RequestData[keyName].is_number()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
comment = std::string("The field value of `") + keyName + "` must be a number."; comment = std::string("The field value of `") + keyName +
"` must be a number.";
return false; return false;
} }
double value = RequestData[keyName]; double value = RequestData[keyName];
if (value < minValue) { if (value < minValue) {
statusCode = RequestStatus::RequestFieldOutOfRange; statusCode = RequestStatus::RequestFieldOutOfRange;
comment = std::string("The field value of `") + keyName + "` is below the minimum of `" + std::to_string(minValue) + "`"; comment = std::string("The field value of `") + keyName +
"` is below the minimum of `" +
std::to_string(minValue) + "`";
return false; return false;
} }
if (value > maxValue) { if (value > maxValue) {
statusCode = RequestStatus::RequestFieldOutOfRange; statusCode = RequestStatus::RequestFieldOutOfRange;
comment = std::string("The field value of `") + keyName + "` is above the maximum of `" + std::to_string(maxValue) + "`"; comment = std::string("The field value of `") + keyName +
"` is above the maximum of `" +
std::to_string(maxValue) + "`";
return false; return false;
} }
return true; return true;
} }
bool Request::ValidateNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue, const double maxValue) const bool Request::ValidateNumber(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment, const double minValue,
const double maxValue) const
{ {
if (!ValidateBasic(keyName, statusCode, comment)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
if (!ValidateOptionalNumber(keyName, statusCode, comment, minValue, maxValue)) if (!ValidateOptionalNumber(keyName, statusCode, comment, minValue,
maxValue))
return false; return false;
return true; return true;
} }
bool Request::ValidateOptionalString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const bool Request::ValidateOptionalString(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty) const
{ {
if (!RequestData[keyName].is_string()) { if (!RequestData[keyName].is_string()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
comment = std::string("The field value of `") + keyName + "` must be a string."; comment = std::string("The field value of `") + keyName +
"` must be a string.";
return false; return false;
} }
if (RequestData[keyName].get<std::string>().empty() && !allowEmpty) { if (RequestData[keyName].get<std::string>().empty() && !allowEmpty) {
statusCode = RequestStatus::RequestFieldEmpty; statusCode = RequestStatus::RequestFieldEmpty;
comment = std::string("The field value of `") + keyName + "` must not be empty."; comment = std::string("The field value of `") + keyName +
"` must not be empty.";
return false; return false;
} }
return true; return true;
} }
bool Request::ValidateString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const bool Request::ValidateString(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment, const bool allowEmpty) const
{ {
if (!ValidateBasic(keyName, statusCode, comment)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
@ -121,18 +148,23 @@ bool Request::ValidateString(const std::string &keyName, RequestStatus::RequestS
return true; return true;
} }
bool Request::ValidateOptionalBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const bool Request::ValidateOptionalBoolean(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const
{ {
if (!RequestData[keyName].is_boolean()) { if (!RequestData[keyName].is_boolean()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
comment = std::string("The field value of `") + keyName + "` must be boolean."; comment = std::string("The field value of `") + keyName +
"` must be boolean.";
return false; return false;
} }
return true; return true;
} }
bool Request::ValidateBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const bool Request::ValidateBoolean(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const
{ {
if (!ValidateBasic(keyName, statusCode, comment)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
@ -143,24 +175,31 @@ bool Request::ValidateBoolean(const std::string &keyName, RequestStatus::Request
return true; return true;
} }
bool Request::ValidateOptionalObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const bool Request::ValidateOptionalObject(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty) const
{ {
if (!RequestData[keyName].is_object()) { if (!RequestData[keyName].is_object()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
comment = std::string("The field value of `") + keyName + "` must be an object."; comment = std::string("The field value of `") + keyName +
"` must be an object.";
return false; return false;
} }
if (RequestData[keyName].empty() && !allowEmpty) { if (RequestData[keyName].empty() && !allowEmpty) {
statusCode = RequestStatus::RequestFieldEmpty; statusCode = RequestStatus::RequestFieldEmpty;
comment = std::string("The field value of `") + keyName + "` must not be empty."; comment = std::string("The field value of `") + keyName +
"` must not be empty.";
return false; return false;
} }
return true; return true;
} }
bool Request::ValidateObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const bool Request::ValidateObject(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment, const bool allowEmpty) const
{ {
if (!ValidateBasic(keyName, statusCode, comment)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
@ -171,24 +210,31 @@ bool Request::ValidateObject(const std::string &keyName, RequestStatus::RequestS
return true; return true;
} }
bool Request::ValidateOptionalArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const bool Request::ValidateOptionalArray(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty) const
{ {
if (!RequestData[keyName].is_array()) { if (!RequestData[keyName].is_array()) {
statusCode = RequestStatus::InvalidRequestFieldType; statusCode = RequestStatus::InvalidRequestFieldType;
comment = std::string("The field value of `") + keyName + "` must be an array."; comment = std::string("The field value of `") + keyName +
"` must be an array.";
return false; return false;
} }
if (RequestData[keyName].empty() && !allowEmpty) { if (RequestData[keyName].empty() && !allowEmpty) {
statusCode = RequestStatus::RequestFieldEmpty; statusCode = RequestStatus::RequestFieldEmpty;
comment = std::string("The field value of `") + keyName + "` must not be empty."; comment = std::string("The field value of `") + keyName +
"` must not be empty.";
return false; return false;
} }
return true; return true;
} }
bool Request::ValidateArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty) const bool Request::ValidateArray(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment, const bool allowEmpty) const
{ {
if (!ValidateBasic(keyName, statusCode, comment)) if (!ValidateBasic(keyName, statusCode, comment))
return false; return false;
@ -199,7 +245,9 @@ bool Request::ValidateArray(const std::string &keyName, RequestStatus::RequestSt
return true; return true;
} }
obs_source_t *Request::ValidateSource(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const obs_source_t *Request::ValidateSource(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const
{ {
if (!ValidateString(keyName, statusCode, comment)) if (!ValidateString(keyName, statusCode, comment))
return nullptr; return nullptr;
@ -209,14 +257,18 @@ obs_source_t *Request::ValidateSource(const std::string &keyName, RequestStatus:
obs_source_t *ret = obs_get_source_by_name(sourceName.c_str()); obs_source_t *ret = obs_get_source_by_name(sourceName.c_str());
if (!ret) { if (!ret) {
statusCode = RequestStatus::ResourceNotFound; statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No source was found by the name of `") + sourceName + "`."; comment = std::string("No source was found by the name of `") +
sourceName + "`.";
return nullptr; return nullptr;
} }
return ret; return ret;
} }
obs_source_t *Request::ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const obs_source_t *Request::ValidateScene(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const ObsWebSocketSceneFilter filter) const
{ {
obs_source_t *ret = ValidateSource(keyName, statusCode, comment); obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
if (!ret) if (!ret)
@ -235,7 +287,8 @@ obs_source_t *Request::ValidateScene(const std::string &keyName, RequestStatus::
statusCode = RequestStatus::InvalidResourceType; statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a scene. (Is group)"; comment = "The specified source is not a scene. (Is group)";
return nullptr; return nullptr;
} else if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY && !isGroup) { } else if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY &&
!isGroup) {
obs_source_release(ret); obs_source_release(ret);
statusCode = RequestStatus::InvalidResourceType; statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a group. (Is scene)"; comment = "The specified source is not a group. (Is scene)";
@ -245,9 +298,13 @@ obs_source_t *Request::ValidateScene(const std::string &keyName, RequestStatus::
return ret; return ret;
} }
obs_scene_t *Request::ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const obs_scene_t *Request::ValidateScene2(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const ObsWebSocketSceneFilter filter) const
{ {
OBSSourceAutoRelease sceneSource = ValidateSource(keyName, statusCode, comment); OBSSourceAutoRelease sceneSource =
ValidateSource(keyName, statusCode, comment);
if (!sceneSource) if (!sceneSource)
return nullptr; return nullptr;
@ -261,21 +318,25 @@ obs_scene_t *Request::ValidateScene2(const std::string &keyName, RequestStatus::
if (isGroup) { if (isGroup) {
if (filter == OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) { if (filter == OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) {
statusCode = RequestStatus::InvalidResourceType; statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a scene. (Is group)"; comment =
"The specified source is not a scene. (Is group)";
return nullptr; return nullptr;
} }
return obs_scene_get_ref(obs_group_from_source(sceneSource)); return obs_scene_get_ref(obs_group_from_source(sceneSource));
} else { } else {
if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY) { if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY) {
statusCode = RequestStatus::InvalidResourceType; statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a group. (Is scene)"; comment =
"The specified source is not a group. (Is scene)";
return nullptr; return nullptr;
} }
return obs_scene_get_ref(obs_scene_from_source(sceneSource)); return obs_scene_get_ref(obs_scene_from_source(sceneSource));
} }
} }
obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const obs_source_t *Request::ValidateInput(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const
{ {
obs_source_t *ret = ValidateSource(keyName, statusCode, comment); obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
if (!ret) if (!ret)
@ -291,9 +352,13 @@ obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::
return ret; return ret;
} }
FilterPair Request::ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const FilterPair Request::ValidateFilter(const std::string &sourceKeyName,
const std::string &filterKeyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const
{ {
obs_source_t *source = ValidateSource(sourceKeyName, statusCode, comment); obs_source_t *source =
ValidateSource(sourceKeyName, statusCode, comment);
if (!source) if (!source)
return FilterPair{source, nullptr}; return FilterPair{source, nullptr};
@ -302,19 +367,26 @@ FilterPair Request::ValidateFilter(const std::string &sourceKeyName, const std::
std::string filterName = RequestData[filterKeyName]; std::string filterName = RequestData[filterKeyName];
obs_source_t *filter = obs_source_get_filter_by_name(source, filterName.c_str()); obs_source_t *filter =
obs_source_get_filter_by_name(source, filterName.c_str());
if (!filter) { if (!filter) {
statusCode = RequestStatus::ResourceNotFound; statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No filter was found in the source `") + RequestData[sourceKeyName].get<std::string>() + "` with the name `" + filterName + "`."; comment = std::string("No filter was found in the source `") +
RequestData[sourceKeyName].get<std::string>() +
"` with the name `" + filterName + "`.";
return FilterPair{source, nullptr}; return FilterPair{source, nullptr};
} }
return FilterPair{source, filter}; return FilterPair{source, filter};
} }
obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const obs_sceneitem_t *Request::ValidateSceneItem(
const std::string &sceneKeyName, const std::string &sceneItemIdKeyName,
RequestStatus::RequestStatus &statusCode, std::string &comment,
const ObsWebSocketSceneFilter filter) const
{ {
OBSSceneAutoRelease scene = ValidateScene2(sceneKeyName, statusCode, comment, filter); OBSSceneAutoRelease scene =
ValidateScene2(sceneKeyName, statusCode, comment, filter);
if (!scene) if (!scene)
return nullptr; return nullptr;
@ -323,10 +395,14 @@ obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, con
int64_t sceneItemId = RequestData[sceneItemIdKeyName]; int64_t sceneItemId = RequestData[sceneItemIdKeyName];
OBSSceneItem sceneItem = obs_scene_find_sceneitem_by_id(scene, sceneItemId); OBSSceneItem sceneItem =
obs_scene_find_sceneitem_by_id(scene, sceneItemId);
if (!sceneItem) { if (!sceneItem) {
statusCode = RequestStatus::ResourceNotFound; statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No scene items were found in scene `") + RequestData[sceneKeyName].get<std::string>() + "` with the ID `" + std::to_string(sceneItemId) + "`."; comment = std::string("No scene items were found in scene `") +
RequestData[sceneKeyName].get<std::string>() +
"` with the ID `" + std::to_string(sceneItemId) +
"`.";
return nullptr; return nullptr;
} }

View File

@ -35,32 +35,89 @@ struct FilterPair {
OBSSourceAutoRelease filter; OBSSourceAutoRelease filter;
}; };
struct Request struct Request {
{ Request(const std::string &requestType,
Request(const std::string &requestType, const json &requestData = nullptr, const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None); const json &requestData = nullptr,
const RequestBatchExecutionType::RequestBatchExecutionType
executionType = RequestBatchExecutionType::None);
// Contains the key and is not null // Contains the key and is not null
bool Contains(const std::string &keyName) const; bool Contains(const std::string &keyName) const;
bool ValidateBasic(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; bool ValidateBasic(const std::string &keyName,
bool ValidateOptionalNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue = -INFINITY, const double maxValue = INFINITY) const; RequestStatus::RequestStatus &statusCode,
bool ValidateNumber(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const double minValue = -INFINITY, const double maxValue = INFINITY) const; std::string &comment) const;
bool ValidateOptionalString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; bool ValidateOptionalNumber(const std::string &keyName,
bool ValidateString(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; RequestStatus::RequestStatus &statusCode,
bool ValidateOptionalBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; std::string &comment,
bool ValidateBoolean(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; const double minValue = -INFINITY,
bool ValidateOptionalObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; const double maxValue = INFINITY) const;
bool ValidateObject(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; bool ValidateNumber(const std::string &keyName,
bool ValidateOptionalArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; RequestStatus::RequestStatus &statusCode,
bool ValidateArray(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; std::string &comment,
const double minValue = -INFINITY,
const double maxValue = INFINITY) const;
bool ValidateOptionalString(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty = false) const;
bool ValidateString(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty = false) const;
bool ValidateOptionalBoolean(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const;
bool ValidateBoolean(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const;
bool ValidateOptionalObject(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty = false) const;
bool ValidateObject(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty = false) const;
bool ValidateOptionalArray(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty = false) const;
bool ValidateArray(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const bool allowEmpty = false) const;
// All return values have incremented refcounts // All return values have incremented refcounts
obs_source_t *ValidateSource(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; obs_source_t *ValidateSource(const std::string &keyName,
obs_source_t *ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const; RequestStatus::RequestStatus &statusCode,
obs_scene_t *ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const; std::string &comment) const;
obs_source_t *ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; obs_source_t *
FilterPair ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; ValidateScene(const std::string &keyName,
obs_sceneitem_t *ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const; RequestStatus::RequestStatus &statusCode,
std::string &comment,
const ObsWebSocketSceneFilter filter =
OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_scene_t *
ValidateScene2(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const ObsWebSocketSceneFilter filter =
OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_source_t *ValidateInput(const std::string &keyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const;
FilterPair ValidateFilter(const std::string &sourceKeyName,
const std::string &filterKeyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment) const;
obs_sceneitem_t *
ValidateSceneItem(const std::string &sceneKeyName,
const std::string &sceneItemIdKeyName,
RequestStatus::RequestStatus &statusCode,
std::string &comment,
const ObsWebSocketSceneFilter filter =
OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
std::string RequestType; std::string RequestType;
bool HasRequestData; bool HasRequestData;

View File

@ -18,9 +18,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestBatchRequest.h" #include "RequestBatchRequest.h"
RequestBatchRequest::RequestBatchRequest(const std::string &requestType, const json &requestData, RequestBatchExecutionType::RequestBatchExecutionType executionType, const json &inputVariables, const json &outputVariables) : RequestBatchRequest::RequestBatchRequest(
Request(requestType, requestData, executionType), const std::string &requestType, const json &requestData,
InputVariables(inputVariables), RequestBatchExecutionType::RequestBatchExecutionType executionType,
OutputVariables(outputVariables) const json &inputVariables, const json &outputVariables)
: Request(requestType, requestData, executionType),
InputVariables(inputVariables),
OutputVariables(outputVariables)
{ {
} }

View File

@ -21,7 +21,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Request.h" #include "Request.h"
struct RequestBatchRequest : Request { struct RequestBatchRequest : Request {
RequestBatchRequest(const std::string &requestType, const json &requestData, RequestBatchExecutionType::RequestBatchExecutionType executionType, const json &inputVariables = nullptr, const json &outputVariables = nullptr); RequestBatchRequest(const std::string &requestType,
const json &requestData,
RequestBatchExecutionType::RequestBatchExecutionType
executionType,
const json &inputVariables = nullptr,
const json &outputVariables = nullptr);
json InputVariables; json InputVariables;
json OutputVariables; json OutputVariables;

View File

@ -19,11 +19,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestResult.h" #include "RequestResult.h"
RequestResult::RequestResult(RequestStatus::RequestStatus statusCode, json responseData, std::string comment) : RequestResult::RequestResult(RequestStatus::RequestStatus statusCode,
StatusCode(statusCode), json responseData, std::string comment)
ResponseData(responseData), : StatusCode(statusCode),
Comment(comment), ResponseData(responseData),
SleepFrames(0) Comment(comment),
SleepFrames(0)
{ {
} }
@ -32,7 +33,8 @@ RequestResult RequestResult::Success(json responseData)
return RequestResult(RequestStatus::Success, responseData, ""); return RequestResult(RequestStatus::Success, responseData, "");
} }
RequestResult RequestResult::Error(RequestStatus::RequestStatus statusCode, std::string comment) RequestResult RequestResult::Error(RequestStatus::RequestStatus statusCode,
std::string comment)
{ {
return RequestResult(statusCode, nullptr, comment); return RequestResult(statusCode, nullptr, comment);
} }

View File

@ -22,11 +22,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../types/RequestStatus.h" #include "../types/RequestStatus.h"
#include "../../utils/Json.h" #include "../../utils/Json.h"
struct RequestResult struct RequestResult {
{ RequestResult(RequestStatus::RequestStatus statusCode =
RequestResult(RequestStatus::RequestStatus statusCode = RequestStatus::Success, json responseData = nullptr, std::string comment = ""); RequestStatus::Success,
json responseData = nullptr, std::string comment = "");
static RequestResult Success(json responseData = nullptr); static RequestResult Success(json responseData = nullptr);
static RequestResult Error(RequestStatus::RequestStatus statusCode, std::string comment = ""); static RequestResult Error(RequestStatus::RequestStatus statusCode,
std::string comment = "");
RequestStatus::RequestStatus StatusCode; RequestStatus::RequestStatus StatusCode;
json ResponseData; json ResponseData;
std::string Comment; std::string Comment;

View File

@ -22,8 +22,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <stdint.h> #include <stdint.h>
namespace RequestBatchExecutionType { namespace RequestBatchExecutionType {
enum RequestBatchExecutionType: int8_t { enum RequestBatchExecutionType : int8_t {
/** /**
* Not a request batch. * Not a request batch.
* *
* @enumIdentifier None * @enumIdentifier None
@ -33,8 +33,8 @@ namespace RequestBatchExecutionType {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
None = -1, None = -1,
/** /**
* A request batch which processes all requests serially, as fast as possible. * A request batch which processes all requests serially, as fast as possible.
* *
* Note: To introduce artificial delay, use the `Sleep` request and the `sleepMillis` request field. * Note: To introduce artificial delay, use the `Sleep` request and the `sleepMillis` request field.
@ -46,8 +46,8 @@ namespace RequestBatchExecutionType {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
SerialRealtime = 0, SerialRealtime = 0,
/** /**
* A request batch type which processes all requests serially, in sync with the graphics thread. Designed to * A request batch type which processes all requests serially, in sync with the graphics thread. Designed to
* provide high accuracy for animations. * provide high accuracy for animations.
* *
@ -60,8 +60,8 @@ namespace RequestBatchExecutionType {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
SerialFrame = 1, SerialFrame = 1,
/** /**
* A request batch type which processes all requests using all available threads in the thread pool. * A request batch type which processes all requests using all available threads in the thread pool.
* *
* Note: This is mainly experimental, and only really shows its colors during requests which require lots of * Note: This is mainly experimental, and only really shows its colors during requests which require lots of
@ -74,11 +74,11 @@ namespace RequestBatchExecutionType {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Parallel = 2, Parallel = 2,
}; };
inline bool IsValid(int8_t executionType) inline bool IsValid(int8_t executionType)
{ {
return executionType >= None && executionType <= Parallel; return executionType >= None && executionType <= Parallel;
} }
} }

View File

@ -20,8 +20,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once #pragma once
namespace RequestStatus { namespace RequestStatus {
enum RequestStatus { enum RequestStatus {
/** /**
* Unknown status, should never be used. * Unknown status, should never be used.
* *
* @enumIdentifier Unknown * @enumIdentifier Unknown
@ -31,9 +31,9 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Unknown = 0, Unknown = 0,
/** /**
* For internal use to signify a successful field check. * For internal use to signify a successful field check.
* *
* @enumIdentifier NoError * @enumIdentifier NoError
@ -43,9 +43,9 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
NoError = 10, NoError = 10,
/** /**
* The request has succeeded. * The request has succeeded.
* *
* @enumIdentifier Success * @enumIdentifier Success
@ -55,9 +55,9 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Success = 100, Success = 100,
/** /**
* The `requestType` field is missing from the request data. * The `requestType` field is missing from the request data.
* *
* @enumIdentifier MissingRequestType * @enumIdentifier MissingRequestType
@ -67,8 +67,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
MissingRequestType = 203, MissingRequestType = 203,
/** /**
* The request type is invalid or does not exist. * The request type is invalid or does not exist.
* *
* @enumIdentifier UnknownRequestType * @enumIdentifier UnknownRequestType
@ -78,8 +78,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
UnknownRequestType = 204, UnknownRequestType = 204,
/** /**
* Generic error code. * Generic error code.
* *
* Note: A comment is required to be provided by obs-websocket. * Note: A comment is required to be provided by obs-websocket.
@ -91,8 +91,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
GenericError = 205, GenericError = 205,
/** /**
* The request batch execution type is not supported. * The request batch execution type is not supported.
* *
* @enumIdentifier UnsupportedRequestBatchExecutionType * @enumIdentifier UnsupportedRequestBatchExecutionType
@ -102,9 +102,9 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
UnsupportedRequestBatchExecutionType = 206, UnsupportedRequestBatchExecutionType = 206,
/** /**
* A required request field is missing. * A required request field is missing.
* *
* @enumIdentifier MissingRequestField * @enumIdentifier MissingRequestField
@ -114,8 +114,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
MissingRequestField = 300, MissingRequestField = 300,
/** /**
* The request does not have a valid requestData object. * The request does not have a valid requestData object.
* *
* @enumIdentifier MissingRequestData * @enumIdentifier MissingRequestData
@ -125,9 +125,9 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
MissingRequestData = 301, MissingRequestData = 301,
/** /**
* Generic invalid request field message. * Generic invalid request field message.
* *
* Note: A comment is required to be provided by obs-websocket. * Note: A comment is required to be provided by obs-websocket.
@ -139,8 +139,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InvalidRequestField = 400, InvalidRequestField = 400,
/** /**
* A request field has the wrong data type. * A request field has the wrong data type.
* *
* @enumIdentifier InvalidRequestFieldType * @enumIdentifier InvalidRequestFieldType
@ -150,8 +150,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InvalidRequestFieldType = 401, InvalidRequestFieldType = 401,
/** /**
* A request field (number) is outside of the allowed range. * A request field (number) is outside of the allowed range.
* *
* @enumIdentifier RequestFieldOutOfRange * @enumIdentifier RequestFieldOutOfRange
@ -161,8 +161,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
RequestFieldOutOfRange = 402, RequestFieldOutOfRange = 402,
/** /**
* A request field (string or array) is empty and cannot be. * A request field (string or array) is empty and cannot be.
* *
* @enumIdentifier RequestFieldEmpty * @enumIdentifier RequestFieldEmpty
@ -172,8 +172,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
RequestFieldEmpty = 403, RequestFieldEmpty = 403,
/** /**
* There are too many request fields (eg. a request takes two optionals, where only one is allowed at a time). * There are too many request fields (eg. a request takes two optionals, where only one is allowed at a time).
* *
* @enumIdentifier TooManyRequestFields * @enumIdentifier TooManyRequestFields
@ -183,9 +183,9 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
TooManyRequestFields = 404, TooManyRequestFields = 404,
/** /**
* An output is running and cannot be in order to perform the request. * An output is running and cannot be in order to perform the request.
* *
* @enumIdentifier OutputRunning * @enumIdentifier OutputRunning
@ -195,8 +195,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
OutputRunning = 500, OutputRunning = 500,
/** /**
* An output is not running and should be. * An output is not running and should be.
* *
* @enumIdentifier OutputNotRunning * @enumIdentifier OutputNotRunning
@ -206,8 +206,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
OutputNotRunning = 501, OutputNotRunning = 501,
/** /**
* An output is paused and should not be. * An output is paused and should not be.
* *
* @enumIdentifier OutputPaused * @enumIdentifier OutputPaused
@ -217,8 +217,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
OutputPaused = 502, OutputPaused = 502,
/** /**
* An output is not paused and should be. * An output is not paused and should be.
* *
* @enumIdentifier OutputNotPaused * @enumIdentifier OutputNotPaused
@ -228,8 +228,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
OutputNotPaused = 503, OutputNotPaused = 503,
/** /**
* An output is disabled and should not be. * An output is disabled and should not be.
* *
* @enumIdentifier OutputDisabled * @enumIdentifier OutputDisabled
@ -239,8 +239,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
OutputDisabled = 504, OutputDisabled = 504,
/** /**
* Studio mode is active and cannot be. * Studio mode is active and cannot be.
* *
* @enumIdentifier StudioModeActive * @enumIdentifier StudioModeActive
@ -250,8 +250,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
StudioModeActive = 505, StudioModeActive = 505,
/** /**
* Studio mode is not active and should be. * Studio mode is not active and should be.
* *
* @enumIdentifier StudioModeNotActive * @enumIdentifier StudioModeNotActive
@ -261,10 +261,9 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
StudioModeNotActive = 506, StudioModeNotActive = 506,
/** /**
* The resource was not found. * The resource was not found.
* *
* Note: Resources are any kind of object in obs-websocket, like inputs, profiles, outputs, etc. * Note: Resources are any kind of object in obs-websocket, like inputs, profiles, outputs, etc.
@ -276,8 +275,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
ResourceNotFound = 600, ResourceNotFound = 600,
/** /**
* The resource already exists. * The resource already exists.
* *
* @enumIdentifier ResourceAlreadyExists * @enumIdentifier ResourceAlreadyExists
@ -287,8 +286,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
ResourceAlreadyExists = 601, ResourceAlreadyExists = 601,
/** /**
* The type of resource found is invalid. * The type of resource found is invalid.
* *
* @enumIdentifier InvalidResourceType * @enumIdentifier InvalidResourceType
@ -298,8 +297,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InvalidResourceType = 602, InvalidResourceType = 602,
/** /**
* There are not enough instances of the resource in order to perform the request. * There are not enough instances of the resource in order to perform the request.
* *
* @enumIdentifier NotEnoughResources * @enumIdentifier NotEnoughResources
@ -309,8 +308,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
NotEnoughResources = 603, NotEnoughResources = 603,
/** /**
* The state of the resource is invalid. For example, if the resource is blocked from being accessed. * The state of the resource is invalid. For example, if the resource is blocked from being accessed.
* *
* @enumIdentifier InvalidResourceState * @enumIdentifier InvalidResourceState
@ -320,8 +319,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InvalidResourceState = 604, InvalidResourceState = 604,
/** /**
* The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind. * The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind.
* *
* @enumIdentifier InvalidInputKind * @enumIdentifier InvalidInputKind
@ -331,8 +330,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InvalidInputKind = 605, InvalidInputKind = 605,
/** /**
* The resource does not support being configured. * The resource does not support being configured.
* *
* This is particularly relevant to transitions, where they do not always have changeable settings. * This is particularly relevant to transitions, where they do not always have changeable settings.
@ -344,8 +343,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
ResourceNotConfigurable = 606, ResourceNotConfigurable = 606,
/** /**
* The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) had the wrong kind. * The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) had the wrong kind.
* *
* @enumIdentifier InvalidFilterKind * @enumIdentifier InvalidFilterKind
@ -355,9 +354,9 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InvalidFilterKind = 607, InvalidFilterKind = 607,
/** /**
* Creating the resource failed. * Creating the resource failed.
* *
* @enumIdentifier ResourceCreationFailed * @enumIdentifier ResourceCreationFailed
@ -367,8 +366,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
ResourceCreationFailed = 700, ResourceCreationFailed = 700,
/** /**
* Performing an action on the resource failed. * Performing an action on the resource failed.
* *
* @enumIdentifier ResourceActionFailed * @enumIdentifier ResourceActionFailed
@ -378,8 +377,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
ResourceActionFailed = 701, ResourceActionFailed = 701,
/** /**
* Processing the request failed unexpectedly. * Processing the request failed unexpectedly.
* *
* Note: A comment is required to be provided by obs-websocket. * Note: A comment is required to be provided by obs-websocket.
@ -391,8 +390,8 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
RequestProcessingFailed = 702, RequestProcessingFailed = 702,
/** /**
* The combination of request fields cannot be used to perform an action. * The combination of request fields cannot be used to perform an action.
* *
* @enumIdentifier CannotAct * @enumIdentifier CannotAct
@ -402,6 +401,6 @@ namespace RequestStatus {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
CannotAct = 703, CannotAct = 703,
}; };
} }

View File

@ -19,8 +19,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Compat.h" #include "Compat.h"
Utils::Compat::StdFunctionRunnable::StdFunctionRunnable(std::function<void()> func) : Utils::Compat::StdFunctionRunnable::StdFunctionRunnable(
cb(std::move(func)) std::function<void()> func)
: cb(std::move(func))
{ {
} }

View File

@ -23,15 +23,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <QRunnable> #include <QRunnable>
namespace Utils { namespace Utils {
namespace Compat { namespace Compat {
// Reimplement QRunnable for std::function. Retrocompatability for Qt < 5.15 // Reimplement QRunnable for std::function. Retrocompatability for Qt < 5.15
class StdFunctionRunnable : public QRunnable { class StdFunctionRunnable : public QRunnable {
std::function<void()> cb; std::function<void()> cb;
public:
StdFunctionRunnable(std::function<void()> func);
void run() override;
};
QRunnable *CreateFunctionRunnable(std::function<void()> func); public:
} StdFunctionRunnable(std::function<void()> func);
void run() override;
};
QRunnable *CreateFunctionRunnable(std::function<void()> func);
}
} }

View File

@ -24,7 +24,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Crypto.h" #include "Crypto.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
static const char allowedChars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; static const char allowedChars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
static const int allowedCharsCount = static_cast<int>(sizeof(allowedChars) - 1); static const int allowedCharsCount = static_cast<int>(sizeof(allowedChars) - 1);
std::string Utils::Crypto::GenerateSalt() std::string Utils::Crypto::GenerateSalt()
@ -42,10 +43,12 @@ std::string Utils::Crypto::GenerateSalt()
return randomChars.toBase64().toStdString(); return randomChars.toBase64().toStdString();
} }
std::string Utils::Crypto::GenerateSecret(std::string password, std::string salt) std::string Utils::Crypto::GenerateSecret(std::string password,
std::string salt)
{ {
// Create challenge hash // Create challenge hash
auto challengeHash = QCryptographicHash(QCryptographicHash::Algorithm::Sha256); auto challengeHash =
QCryptographicHash(QCryptographicHash::Algorithm::Sha256);
// Add password bytes to hash // Add password bytes to hash
challengeHash.addData(QByteArray::fromStdString(password)); challengeHash.addData(QByteArray::fromStdString(password));
// Add salt bytes to hash // Add salt bytes to hash
@ -55,7 +58,9 @@ std::string Utils::Crypto::GenerateSecret(std::string password, std::string salt
return challengeHash.result().toBase64().toStdString(); return challengeHash.result().toBase64().toStdString();
} }
bool Utils::Crypto::CheckAuthenticationString(std::string secret, std::string challenge, std::string authenticationString) bool Utils::Crypto::CheckAuthenticationString(std::string secret,
std::string challenge,
std::string authenticationString)
{ {
// Concatenate auth secret with the challenge sent to the user // Concatenate auth secret with the challenge sent to the user
QString secretAndChallenge = ""; QString secretAndChallenge = "";
@ -63,13 +68,13 @@ bool Utils::Crypto::CheckAuthenticationString(std::string secret, std::string ch
secretAndChallenge += QString::fromStdString(challenge); secretAndChallenge += QString::fromStdString(challenge);
// Generate a SHA256 hash of secretAndChallenge // Generate a SHA256 hash of secretAndChallenge
auto hash = QCryptographicHash::hash( auto hash =
secretAndChallenge.toUtf8(), QCryptographicHash::hash(secretAndChallenge.toUtf8(),
QCryptographicHash::Algorithm::Sha256 QCryptographicHash::Algorithm::Sha256);
);
// Encode the SHA256 hash to Base64 // Encode the SHA256 hash to Base64
std::string expectedAuthenticationString = hash.toBase64().toStdString(); std::string expectedAuthenticationString =
hash.toBase64().toStdString();
return (authenticationString == expectedAuthenticationString); return (authenticationString == expectedAuthenticationString);
} }

View File

@ -23,10 +23,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <QString> #include <QString>
namespace Utils { namespace Utils {
namespace Crypto { namespace Crypto {
std::string GenerateSalt(); std::string GenerateSalt();
std::string GenerateSecret(std::string password, std::string salt); std::string GenerateSecret(std::string password, std::string salt);
bool CheckAuthenticationString(std::string secret, std::string challenge, std::string authenticationString); bool CheckAuthenticationString(std::string secret, std::string challenge,
std::string GeneratePassword(size_t length = 16); std::string authenticationString);
} std::string GeneratePassword(size_t length = 16);
}
} }

View File

@ -45,7 +45,7 @@ void obs_data_set_json_array(obs_data_t *d, const char *key, json j)
{ {
obs_data_array_t *array = obs_data_array_create(); obs_data_array_t *array = obs_data_array_create();
for (auto& [key, value] : j.items()) { for (auto &[key, value] : j.items()) {
if (!value.is_object()) if (!value.is_object())
continue; continue;
@ -61,17 +61,19 @@ void obs_data_set_json_array(obs_data_t *d, const char *key, json j)
void obs_data_set_json_object_item(obs_data_t *d, json j) void obs_data_set_json_object_item(obs_data_t *d, json j)
{ {
for (auto& [key, value] : j.items()) { for (auto &[key, value] : j.items()) {
if (value.is_object()) { if (value.is_object()) {
obs_data_set_json_object(d, key.c_str(), value); obs_data_set_json_object(d, key.c_str(), value);
} else if (value.is_array()) { } else if (value.is_array()) {
obs_data_set_json_array(d, key.c_str(), value); obs_data_set_json_array(d, key.c_str(), value);
} else if (value.is_string()) { } else if (value.is_string()) {
obs_data_set_string(d, key.c_str(), value.get<std::string>().c_str()); obs_data_set_string(d, key.c_str(),
value.get<std::string>().c_str());
} else if (value.is_number_integer()) { } else if (value.is_number_integer()) {
obs_data_set_int(d, key.c_str(), value.get<int64_t>()); obs_data_set_int(d, key.c_str(), value.get<int64_t>());
} else if (value.is_number_float()) { } else if (value.is_number_float()) {
obs_data_set_double(d, key.c_str(), value.get<double>()); obs_data_set_double(d, key.c_str(),
value.get<double>());
} else if (value.is_boolean()) { } else if (value.is_boolean()) {
obs_data_set_bool(d, key.c_str(), value.get<bool>()); obs_data_set_bool(d, key.c_str(), value.get<bool>());
} }
@ -114,13 +116,15 @@ void set_json_bool(json *j, const char *name, obs_data_item_t *item)
bool val = obs_data_item_get_bool(item); bool val = obs_data_item_get_bool(item);
j->emplace(name, val); j->emplace(name, val);
} }
void set_json_object(json *j, const char *name, obs_data_item_t *item, bool includeDefault) void set_json_object(json *j, const char *name, obs_data_item_t *item,
bool includeDefault)
{ {
obs_data_t *obj = obs_data_item_get_obj(item); obs_data_t *obj = obs_data_item_get_obj(item);
j->emplace(name, Utils::Json::ObsDataToJson(obj, includeDefault)); j->emplace(name, Utils::Json::ObsDataToJson(obj, includeDefault));
obs_data_release(obj); obs_data_release(obj);
} }
void set_json_array(json *j, const char *name, obs_data_item_t *item, bool includeDefault) void set_json_array(json *j, const char *name, obs_data_item_t *item,
bool includeDefault)
{ {
json jArray = json::array(); json jArray = json::array();
obs_data_array_t *array = obs_data_item_get_array(item); obs_data_array_t *array = obs_data_item_get_array(item);
@ -128,7 +132,8 @@ void set_json_array(json *j, const char *name, obs_data_item_t *item, bool inclu
for (size_t idx = 0; idx < count; idx++) { for (size_t idx = 0; idx < count; idx++) {
obs_data_t *subItem = obs_data_array_item(array, idx); obs_data_t *subItem = obs_data_array_item(array, idx);
json jItem = Utils::Json::ObsDataToJson(subItem, includeDefault); json jItem =
Utils::Json::ObsDataToJson(subItem, includeDefault);
obs_data_release(subItem); obs_data_release(subItem);
jArray.push_back(jItem); jArray.push_back(jItem);
} }
@ -153,23 +158,22 @@ json Utils::Json::ObsDataToJson(obs_data_t *d, bool includeDefault)
continue; continue;
switch (type) { switch (type) {
case OBS_DATA_STRING: case OBS_DATA_STRING:
set_json_string(&j, name, item); set_json_string(&j, name, item);
break; break;
case OBS_DATA_NUMBER: case OBS_DATA_NUMBER:
set_json_number(&j, name, item); set_json_number(&j, name, item);
break; break;
case OBS_DATA_BOOLEAN: case OBS_DATA_BOOLEAN:
set_json_bool(&j, name, item); set_json_bool(&j, name, item);
break; break;
case OBS_DATA_OBJECT: case OBS_DATA_OBJECT:
set_json_object(&j, name, item, includeDefault); set_json_object(&j, name, item, includeDefault);
break; break;
case OBS_DATA_ARRAY: case OBS_DATA_ARRAY:
set_json_array(&j, name, item, includeDefault); set_json_array(&j, name, item, includeDefault);
break; break;
default: default:;
;
} }
} }
@ -184,15 +188,19 @@ bool Utils::Json::GetJsonFileContent(std::string fileName, json &content)
try { try {
content = json::parse(textContent); content = json::parse(textContent);
} catch (json::parse_error& e) { } catch (json::parse_error &e) {
blog(LOG_WARNING, "Failed to decode content of JSON file `%s`. Error: %s", fileName.c_str(), e.what()); blog(LOG_WARNING,
"Failed to decode content of JSON file `%s`. Error: %s",
fileName.c_str(), e.what());
return false; return false;
} }
return true; return true;
} }
bool Utils::Json::SetJsonFileContent(std::string fileName, const json &content, bool createNew) bool Utils::Json::SetJsonFileContent(std::string fileName, const json &content,
bool createNew)
{ {
std::string textContent = content.dump(2); std::string textContent = content.dump(2);
return Utils::Platform::SetTextFileContent(fileName, textContent, createNew); return Utils::Platform::SetTextFileContent(fileName, textContent,
createNew);
} }

View File

@ -26,12 +26,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
using json = nlohmann::json; using json = nlohmann::json;
namespace Utils { namespace Utils {
namespace Json { namespace Json {
bool JsonArrayIsValidObsArray(const json &j); bool JsonArrayIsValidObsArray(const json &j);
obs_data_t *JsonToObsData(json j); obs_data_t *JsonToObsData(json j);
json ObsDataToJson(obs_data_t *d, bool includeDefault = false); json ObsDataToJson(obs_data_t *d, bool includeDefault = false);
bool GetJsonFileContent(std::string fileName, json &content); bool GetJsonFileContent(std::string fileName, json &content);
bool SetJsonFileContent(std::string fileName, const json &content, bool createNew = true); bool SetJsonFileContent(std::string fileName, const json &content,
static inline bool Contains(const json &j, std::string key) { return j.contains(key) && !j[key].is_null(); } bool createNew = true);
} static inline bool Contains(const json &j, std::string key)
{
return j.contains(key) && !j[key].is_null();
}
}
} }

View File

@ -26,43 +26,65 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Json.h" #include "Json.h"
// Autorelease object definitions // Autorelease object definitions
inline void ___properties_dummy_addref(obs_properties_t*){} inline void ___properties_dummy_addref(obs_properties_t *) {}
using OBSPropertiesAutoDestroy = OBSRef<obs_properties_t*, ___properties_dummy_addref, obs_properties_destroy>; using OBSPropertiesAutoDestroy =
OBSRef<obs_properties_t *, ___properties_dummy_addref,
obs_properties_destroy>;
#if !defined(OBS_AUTORELEASE) #if !defined(OBS_AUTORELEASE)
inline void ___source_dummy_addref(obs_source_t*){} inline void ___source_dummy_addref(obs_source_t *) {}
inline void ___scene_dummy_addref(obs_scene_t*){} inline void ___scene_dummy_addref(obs_scene_t *) {}
inline void ___sceneitem_dummy_addref(obs_sceneitem_t*){} inline void ___sceneitem_dummy_addref(obs_sceneitem_t *) {}
inline void ___data_dummy_addref(obs_data_t*){} inline void ___data_dummy_addref(obs_data_t *) {}
inline void ___data_array_dummy_addref(obs_data_array_t*){} inline void ___data_array_dummy_addref(obs_data_array_t *) {}
inline void ___output_dummy_addref(obs_output_t*){} inline void ___output_dummy_addref(obs_output_t *) {}
inline void ___encoder_dummy_addref(obs_encoder_t *){} inline void ___encoder_dummy_addref(obs_encoder_t *) {}
inline void ___service_dummy_addref(obs_service_t *){} inline void ___service_dummy_addref(obs_service_t *) {}
inline void ___weak_source_dummy_addref(obs_weak_source_t*){} inline void ___weak_source_dummy_addref(obs_weak_source_t *) {}
inline void ___weak_output_dummy_addref(obs_weak_output_t *){} inline void ___weak_output_dummy_addref(obs_weak_output_t *) {}
inline void ___weak_encoder_dummy_addref(obs_weak_encoder_t *){} inline void ___weak_encoder_dummy_addref(obs_weak_encoder_t *) {}
inline void ___weak_service_dummy_addref(obs_weak_service_t *){} inline void ___weak_service_dummy_addref(obs_weak_service_t *) {}
using OBSSourceAutoRelease = OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>; using OBSSourceAutoRelease =
using OBSSceneAutoRelease = OBSRef<obs_scene_t*, ___scene_dummy_addref, obs_scene_release>; OBSRef<obs_source_t *, ___source_dummy_addref, obs_source_release>;
using OBSSceneItemAutoRelease = OBSRef<obs_sceneitem_t*, ___sceneitem_dummy_addref, obs_sceneitem_release>; using OBSSceneAutoRelease =
using OBSDataAutoRelease = OBSRef<obs_data_t*, ___data_dummy_addref, obs_data_release>; OBSRef<obs_scene_t *, ___scene_dummy_addref, obs_scene_release>;
using OBSDataArrayAutoRelease = OBSRef<obs_data_array_t*, ___data_array_dummy_addref, obs_data_array_release>; using OBSSceneItemAutoRelease =
using OBSOutputAutoRelease = OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>; OBSRef<obs_sceneitem_t *, ___sceneitem_dummy_addref,
using OBSEncoderAutoRelease = OBSRef<obs_encoder_t *, ___encoder_dummy_addref, obs_encoder_release>; obs_sceneitem_release>;
using OBSServiceAutoRelease = OBSRef<obs_service_t *, ___service_dummy_addref, obs_service_release>; using OBSDataAutoRelease =
OBSRef<obs_data_t *, ___data_dummy_addref, obs_data_release>;
using OBSDataArrayAutoRelease =
OBSRef<obs_data_array_t *, ___data_array_dummy_addref,
obs_data_array_release>;
using OBSOutputAutoRelease =
OBSRef<obs_output_t *, ___output_dummy_addref, obs_output_release>;
using OBSEncoderAutoRelease =
OBSRef<obs_encoder_t *, ___encoder_dummy_addref, obs_encoder_release>;
using OBSServiceAutoRelease =
OBSRef<obs_service_t *, ___service_dummy_addref, obs_service_release>;
using OBSWeakSourceAutoRelease = OBSRef<obs_weak_source_t*, ___weak_source_dummy_addref, obs_weak_source_release>; using OBSWeakSourceAutoRelease =
using OBSWeakOutputAutoRelease = OBSRef<obs_weak_output_t *, ___weak_output_dummy_addref, obs_weak_output_release>; OBSRef<obs_weak_source_t *, ___weak_source_dummy_addref,
using OBSWeakEncoderAutoRelease = OBSRef<obs_weak_encoder_t *, ___weak_encoder_dummy_addref, obs_weak_encoder_release>; obs_weak_source_release>;
using OBSWeakServiceAutoRelease = OBSRef<obs_weak_service_t *, ___weak_service_dummy_addref, obs_weak_service_release>; using OBSWeakOutputAutoRelease =
OBSRef<obs_weak_output_t *, ___weak_output_dummy_addref,
obs_weak_output_release>;
using OBSWeakEncoderAutoRelease =
OBSRef<obs_weak_encoder_t *, ___weak_encoder_dummy_addref,
obs_weak_encoder_release>;
using OBSWeakServiceAutoRelease =
OBSRef<obs_weak_service_t *, ___weak_service_dummy_addref,
obs_weak_service_release>;
#endif #endif
template <typename T> T* GetCalldataPointer(const calldata_t *data, const char* name) { template<typename T>
T *GetCalldataPointer(const calldata_t *data, const char *name)
{
void *ptr = nullptr; void *ptr = nullptr;
calldata_get_ptr(data, name, &ptr); calldata_get_ptr(data, name, &ptr);
return static_cast<T*>(ptr); return static_cast<T *>(ptr);
} }
enum ObsOutputState { enum ObsOutputState {
@ -150,69 +172,86 @@ enum ObsMediaInputAction {
}; };
namespace Utils { namespace Utils {
namespace Obs { namespace Obs {
namespace StringHelper { namespace StringHelper {
std::string GetObsVersion(); std::string GetObsVersion();
std::string GetCurrentSceneCollection(); std::string GetCurrentSceneCollection();
std::string GetCurrentProfile(); std::string GetCurrentProfile();
std::string GetCurrentProfilePath(); std::string GetCurrentProfilePath();
std::string GetCurrentRecordOutputPath(); std::string GetCurrentRecordOutputPath();
std::string GetSourceType(obs_source_t *source); std::string GetSourceType(obs_source_t *source);
std::string GetInputMonitorType(enum obs_monitoring_type monitorType); std::string GetInputMonitorType(enum obs_monitoring_type monitorType);
std::string GetInputMonitorType(obs_source_t *input); std::string GetInputMonitorType(obs_source_t *input);
std::string GetMediaInputState(obs_source_t *input); std::string GetMediaInputState(obs_source_t *input);
std::string GetLastReplayBufferFilePath(); std::string GetLastReplayBufferFilePath();
std::string GetSceneItemBoundsType(enum obs_bounds_type type); std::string GetSceneItemBoundsType(enum obs_bounds_type type);
std::string GetSceneItemBlendMode(enum obs_blending_type mode); std::string GetSceneItemBlendMode(enum obs_blending_type mode);
std::string DurationToTimecode(uint64_t); std::string DurationToTimecode(uint64_t);
std::string GetOutputState(ObsOutputState state); std::string GetOutputState(ObsOutputState state);
} }
namespace EnumHelper { namespace EnumHelper {
enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType); enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType);
enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction); enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction);
enum obs_blending_type GetSceneItemBlendMode(std::string mode); enum obs_blending_type GetSceneItemBlendMode(std::string mode);
} }
namespace NumberHelper { namespace NumberHelper {
uint64_t GetOutputDuration(obs_output_t *output); uint64_t GetOutputDuration(obs_output_t *output);
size_t GetSceneCount(); size_t GetSceneCount();
size_t GetSourceFilterIndex(obs_source_t *source, obs_source_t *filter); size_t GetSourceFilterIndex(obs_source_t *source, obs_source_t *filter);
} }
namespace ArrayHelper { namespace ArrayHelper {
std::vector<std::string> GetSceneCollectionList(); std::vector<std::string> GetSceneCollectionList();
std::vector<std::string> GetProfileList(); std::vector<std::string> GetProfileList();
std::vector<obs_hotkey_t *> GetHotkeyList(); std::vector<obs_hotkey_t *> GetHotkeyList();
std::vector<std::string> GetHotkeyNameList(); std::vector<std::string> GetHotkeyNameList();
std::vector<json> GetSceneList(); std::vector<json> GetSceneList();
std::vector<std::string> GetGroupList(); std::vector<std::string> GetGroupList();
std::vector<json> GetSceneItemList(obs_scene_t *scene, bool basic = false); std::vector<json> GetSceneItemList(obs_scene_t *scene, bool basic = false);
std::vector<json> GetInputList(std::string inputKind = ""); std::vector<json> GetInputList(std::string inputKind = "");
std::vector<std::string> GetInputKindList(bool unversioned = false, bool includeDisabled = false); std::vector<std::string> GetInputKindList(bool unversioned = false,
std::vector<json> GetListPropertyItems(obs_property_t *property); bool includeDisabled = false);
std::vector<std::string> GetTransitionKindList(); std::vector<json> GetListPropertyItems(obs_property_t *property);
std::vector<json> GetSceneTransitionList(); std::vector<std::string> GetTransitionKindList();
std::vector<json> GetSourceFilterList(obs_source_t *source); std::vector<json> GetSceneTransitionList();
std::vector<std::string> GetFilterKindList(); std::vector<json> GetSourceFilterList(obs_source_t *source);
} std::vector<std::string> GetFilterKindList();
}
namespace ObjectHelper {
json GetStats(); namespace ObjectHelper {
json GetSceneItemTransform(obs_sceneitem_t *item); json GetStats();
} json GetSceneItemTransform(obs_sceneitem_t *item);
}
namespace SearchHelper {
obs_hotkey_t *GetHotkeyByName(std::string name); namespace SearchHelper {
obs_source_t *GetSceneTransitionByName(std::string name); // Increments source ref. Use OBSSourceAutoRelease obs_hotkey_t *GetHotkeyByName(std::string name);
obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name, int offset = 0); // Increments ref. Use OBSSceneItemAutoRelease obs_source_t *GetSceneTransitionByName(
} std::string name); // Increments source ref. Use OBSSourceAutoRelease
obs_sceneitem_t *GetSceneItemByName(
namespace ActionHelper { obs_scene_t *scene, std::string name,
obs_sceneitem_t *CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled = true, obs_transform_info *sceneItemTransform = nullptr, obs_sceneitem_crop *sceneItemCrop = nullptr); // Increments ref. Use OBSSceneItemAutoRelease int offset = 0); // Increments ref. Use OBSSceneItemAutoRelease
obs_sceneitem_t *CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled = true); // Increments ref. Use OBSSceneItemAutoRelease }
obs_source_t *CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings); // Increments source ref. Use OBSSourceAutoRelease
void SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter, size_t index); namespace ActionHelper {
} obs_sceneitem_t *
} CreateSceneItem(obs_source_t *source, obs_scene_t *scene,
bool sceneItemEnabled = true,
obs_transform_info *sceneItemTransform = nullptr,
obs_sceneitem_crop *sceneItemCrop =
nullptr); // Increments ref. Use OBSSceneItemAutoRelease
obs_sceneitem_t *
CreateInput(std::string inputName, std::string inputKind,
obs_data_t *inputSettings, obs_scene_t *scene,
bool sceneItemEnabled =
true); // Increments ref. Use OBSSceneItemAutoRelease
obs_source_t *CreateSourceFilter(
obs_source_t *source, std::string filterName, std::string filterKind,
obs_data_t *
filterSettings); // Increments source ref. Use OBSSourceAutoRelease
void SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter,
size_t index);
}
}
} }

View File

@ -20,20 +20,21 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
struct CreateSceneItemData { struct CreateSceneItemData {
obs_source_t *source; // In obs_source_t *source; // In
bool sceneItemEnabled; // In bool sceneItemEnabled; // In
obs_transform_info *sceneItemTransform = nullptr; // In obs_transform_info *sceneItemTransform = nullptr; // In
obs_sceneitem_crop *sceneItemCrop = nullptr; // In obs_sceneitem_crop *sceneItemCrop = nullptr; // In
OBSSceneItem sceneItem; // Out OBSSceneItem sceneItem; // Out
}; };
void CreateSceneItemHelper(void *_data, obs_scene_t *scene) void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
{ {
auto *data = static_cast<CreateSceneItemData*>(_data); auto *data = static_cast<CreateSceneItemData *>(_data);
data->sceneItem = obs_scene_add(scene, data->source); data->sceneItem = obs_scene_add(scene, data->source);
if (data->sceneItemTransform) if (data->sceneItemTransform)
obs_sceneitem_set_info(data->sceneItem, data->sceneItemTransform); obs_sceneitem_set_info(data->sceneItem,
data->sceneItemTransform);
if (data->sceneItemCrop) if (data->sceneItemCrop)
obs_sceneitem_set_crop(data->sceneItem, data->sceneItemCrop); obs_sceneitem_set_crop(data->sceneItem, data->sceneItemCrop);
@ -41,7 +42,10 @@ void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
obs_sceneitem_set_visible(data->sceneItem, data->sceneItemEnabled); obs_sceneitem_set_visible(data->sceneItem, data->sceneItemEnabled);
} }
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled, obs_transform_info *sceneItemTransform, obs_sceneitem_crop *sceneItemCrop) obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(
obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled,
obs_transform_info *sceneItemTransform,
obs_sceneitem_crop *sceneItemCrop)
{ {
// Sanity check for valid scene // Sanity check for valid scene
if (!(source && scene)) if (!(source && scene))
@ -64,10 +68,13 @@ obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source,
return data.sceneItem; return data.sceneItem;
} }
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled) obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(
std::string inputName, std::string inputKind, obs_data_t *inputSettings,
obs_scene_t *scene, bool sceneItemEnabled)
{ {
// Create the input // Create the input
OBSSourceAutoRelease input = obs_source_create(inputKind.c_str(), inputName.c_str(), inputSettings, nullptr); OBSSourceAutoRelease input = obs_source_create(
inputKind.c_str(), inputName.c_str(), inputSettings, nullptr);
// Check that everything was created properly // Check that everything was created properly
if (!input) if (!input)
@ -76,7 +83,8 @@ obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(std::string inputName, st
// Apparently not all default input properties actually get applied on creation (smh) // Apparently not all default input properties actually get applied on creation (smh)
uint32_t flags = obs_source_get_output_flags(input); uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0) if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0)
obs_source_set_monitoring_type(input, OBS_MONITORING_TYPE_MONITOR_ONLY); obs_source_set_monitoring_type(
input, OBS_MONITORING_TYPE_MONITOR_ONLY);
// Create a scene item for the input // Create a scene item for the input
obs_sceneitem_t *ret = CreateSceneItem(input, scene, sceneItemEnabled); obs_sceneitem_t *ret = CreateSceneItem(input, scene, sceneItemEnabled);
@ -88,9 +96,12 @@ obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(std::string inputName, st
return ret; return ret;
} }
obs_source_t *Utils::Obs::ActionHelper::CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings) obs_source_t *Utils::Obs::ActionHelper::CreateSourceFilter(
obs_source_t *source, std::string filterName, std::string filterKind,
obs_data_t *filterSettings)
{ {
obs_source_t *filter = obs_source_create_private(filterKind.c_str(), filterName.c_str(), filterSettings); obs_source_t *filter = obs_source_create_private(
filterKind.c_str(), filterName.c_str(), filterSettings);
if (!filter) if (!filter)
return nullptr; return nullptr;
@ -100,12 +111,16 @@ obs_source_t *Utils::Obs::ActionHelper::CreateSourceFilter(obs_source_t *source,
return filter; return filter;
} }
void Utils::Obs::ActionHelper::SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter, size_t index) void Utils::Obs::ActionHelper::SetSourceFilterIndex(obs_source_t *source,
obs_source_t *filter,
size_t index)
{ {
size_t currentIndex = Utils::Obs::NumberHelper::GetSourceFilterIndex(source, filter); size_t currentIndex =
obs_order_movement direction = index > currentIndex ? OBS_ORDER_MOVE_DOWN : OBS_ORDER_MOVE_UP; Utils::Obs::NumberHelper::GetSourceFilterIndex(source, filter);
obs_order_movement direction =
index > currentIndex ? OBS_ORDER_MOVE_DOWN : OBS_ORDER_MOVE_UP;
while(currentIndex != index) { while (currentIndex != index) {
obs_source_filter_set_order(source, filter, direction); obs_source_filter_set_order(source, filter, direction);
if (direction == OBS_ORDER_MOVE_DOWN) if (direction == OBS_ORDER_MOVE_DOWN)

View File

@ -29,7 +29,7 @@ static std::vector<std::string> ConvertStringArray(char **array)
return ret; return ret;
size_t index = 0; size_t index = 0;
char* value = nullptr; char *value = nullptr;
do { do {
value = array[index]; value = array[index];
if (value) if (value)
@ -42,7 +42,7 @@ static std::vector<std::string> ConvertStringArray(char **array)
std::vector<std::string> Utils::Obs::ArrayHelper::GetSceneCollectionList() std::vector<std::string> Utils::Obs::ArrayHelper::GetSceneCollectionList()
{ {
char** sceneCollections = obs_frontend_get_scene_collections(); char **sceneCollections = obs_frontend_get_scene_collections();
auto ret = ConvertStringArray(sceneCollections); auto ret = ConvertStringArray(sceneCollections);
bfree(sceneCollections); bfree(sceneCollections);
return ret; return ret;
@ -50,7 +50,7 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetSceneCollectionList()
std::vector<std::string> Utils::Obs::ArrayHelper::GetProfileList() std::vector<std::string> Utils::Obs::ArrayHelper::GetProfileList()
{ {
char** profiles = obs_frontend_get_profiles(); char **profiles = obs_frontend_get_profiles();
auto ret = ConvertStringArray(profiles); auto ret = ConvertStringArray(profiles);
bfree(profiles); bfree(profiles);
return ret; return ret;
@ -60,13 +60,16 @@ std::vector<obs_hotkey_t *> Utils::Obs::ArrayHelper::GetHotkeyList()
{ {
std::vector<obs_hotkey_t *> ret; std::vector<obs_hotkey_t *> ret;
obs_enum_hotkeys([](void* data, obs_hotkey_id, obs_hotkey_t* hotkey) { obs_enum_hotkeys(
auto ret = static_cast<std::vector<obs_hotkey_t *> *>(data); [](void *data, obs_hotkey_id, obs_hotkey_t *hotkey) {
auto ret = static_cast<std::vector<obs_hotkey_t *> *>(
data);
ret->push_back(hotkey); ret->push_back(hotkey);
return true; return true;
}, &ret); },
&ret);
return ret; return ret;
} }
@ -112,7 +115,7 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetGroupList()
std::vector<std::string> ret; std::vector<std::string> ret;
auto cb = [](void *priv_data, obs_source_t *scene) { auto cb = [](void *priv_data, obs_source_t *scene) {
auto ret = static_cast<std::vector<std::string>*>(priv_data); auto ret = static_cast<std::vector<std::string> *>(priv_data);
if (!obs_source_is_group(scene)) if (!obs_source_is_group(scene))
return true; return true;
@ -127,36 +130,48 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetGroupList()
return ret; return ret;
} }
std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene, bool basic) std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene,
bool basic)
{ {
std::pair<std::vector<json>, bool> enumData; std::pair<std::vector<json>, bool> enumData;
enumData.second = basic; enumData.second = basic;
obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) { obs_scene_enum_items(
auto enumData = static_cast<std::pair<std::vector<json>, bool>*>(param); scene,
[](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
auto enumData = static_cast<
std::pair<std::vector<json>, bool> *>(param);
json item; json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem); item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
// Should be slightly faster than calling obs_sceneitem_get_order_position() // Should be slightly faster than calling obs_sceneitem_get_order_position()
item["sceneItemIndex"] = enumData->first.size(); item["sceneItemIndex"] = enumData->first.size();
if (!enumData->second) { if (!enumData->second) {
OBSSource itemSource = obs_sceneitem_get_source(sceneItem); OBSSource itemSource =
item["sourceName"] = obs_source_get_name(itemSource); obs_sceneitem_get_source(sceneItem);
item["sourceType"] = StringHelper::GetSourceType(itemSource); item["sourceName"] =
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT) obs_source_get_name(itemSource);
item["inputKind"] = obs_source_get_id(itemSource); item["sourceType"] =
else StringHelper::GetSourceType(itemSource);
item["inputKind"] = nullptr; if (obs_source_get_type(itemSource) ==
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE) OBS_SOURCE_TYPE_INPUT)
item["isGroup"] = obs_source_is_group(itemSource); item["inputKind"] =
else obs_source_get_id(itemSource);
item["isGroup"] = nullptr; else
} item["inputKind"] = nullptr;
if (obs_source_get_type(itemSource) ==
OBS_SOURCE_TYPE_SCENE)
item["isGroup"] =
obs_source_is_group(itemSource);
else
item["isGroup"] = nullptr;
}
enumData->first.push_back(item); enumData->first.push_back(item);
return true; return true;
}, &enumData); },
&enumData);
return enumData.first; return enumData.first;
} }
@ -176,17 +191,19 @@ std::vector<json> Utils::Obs::ArrayHelper::GetInputList(std::string inputKind)
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return true; return true;
auto inputInfo = static_cast<EnumInputInfo*>(param); auto inputInfo = static_cast<EnumInputInfo *>(param);
std::string inputKind = obs_source_get_id(input); std::string inputKind = obs_source_get_id(input);
if (!inputInfo->inputKind.empty() && inputInfo->inputKind != inputKind) if (!inputInfo->inputKind.empty() &&
inputInfo->inputKind != inputKind)
return true; return true;
json inputJson; json inputJson;
inputJson["inputName"] = obs_source_get_name(input); inputJson["inputName"] = obs_source_get_name(input);
inputJson["inputKind"] = inputKind; inputJson["inputKind"] = inputKind;
inputJson["unversionedInputKind"] = obs_source_get_unversioned_id(input); inputJson["unversionedInputKind"] =
obs_source_get_unversioned_id(input);
inputInfo->inputs.push_back(inputJson); inputInfo->inputs.push_back(inputJson);
return true; return true;
@ -197,7 +214,9 @@ std::vector<json> Utils::Obs::ArrayHelper::GetInputList(std::string inputKind)
return inputInfo.inputs; return inputInfo.inputs;
} }
std::vector<std::string> Utils::Obs::ArrayHelper::GetInputKindList(bool unversioned, bool includeDisabled) std::vector<std::string>
Utils::Obs::ArrayHelper::GetInputKindList(bool unversioned,
bool includeDisabled)
{ {
std::vector<std::string> ret; std::vector<std::string> ret;
@ -219,7 +238,8 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetInputKindList(bool unversio
return ret; return ret;
} }
std::vector<json> Utils::Obs::ArrayHelper::GetListPropertyItems(obs_property_t *property) std::vector<json>
Utils::Obs::ArrayHelper::GetListPropertyItems(obs_property_t *property)
{ {
std::vector<json> ret; std::vector<json> ret;
@ -231,13 +251,17 @@ std::vector<json> Utils::Obs::ArrayHelper::GetListPropertyItems(obs_property_t *
for (size_t i = 0; i < itemCount; i++) { for (size_t i = 0; i < itemCount; i++) {
json itemData; json itemData;
itemData["itemName"] = obs_property_list_item_name(property, i); itemData["itemName"] = obs_property_list_item_name(property, i);
itemData["itemEnabled"] = !obs_property_list_item_disabled(property, i); itemData["itemEnabled"] =
!obs_property_list_item_disabled(property, i);
if (itemFormat == OBS_COMBO_FORMAT_INT) { if (itemFormat == OBS_COMBO_FORMAT_INT) {
itemData["itemValue"] = obs_property_list_item_int(property, i); itemData["itemValue"] =
obs_property_list_item_int(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_FLOAT) { } else if (itemFormat == OBS_COMBO_FORMAT_FLOAT) {
itemData["itemValue"] = obs_property_list_item_float(property, i); itemData["itemValue"] =
obs_property_list_item_float(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_STRING) { } else if (itemFormat == OBS_COMBO_FORMAT_STRING) {
itemData["itemValue"] = obs_property_list_item_string(property, i); itemData["itemValue"] =
obs_property_list_item_string(property, i);
} else { } else {
itemData["itemValue"] = nullptr; itemData["itemValue"] = nullptr;
} }
@ -269,10 +293,14 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneTransitionList()
for (size_t i = 0; i < transitionList.sources.num; i++) { for (size_t i = 0; i < transitionList.sources.num; i++) {
obs_source_t *transition = transitionList.sources.array[i]; obs_source_t *transition = transitionList.sources.array[i];
json transitionJson; json transitionJson;
transitionJson["transitionName"] = obs_source_get_name(transition); transitionJson["transitionName"] =
transitionJson["transitionKind"] = obs_source_get_id(transition); obs_source_get_name(transition);
transitionJson["transitionFixed"] = obs_transition_fixed(transition); transitionJson["transitionKind"] =
transitionJson["transitionConfigurable"] = obs_source_configurable(transition); obs_source_get_id(transition);
transitionJson["transitionFixed"] =
obs_transition_fixed(transition);
transitionJson["transitionConfigurable"] =
obs_source_configurable(transition);
ret.push_back(transitionJson); ret.push_back(transitionJson);
} }
@ -287,18 +315,20 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetFilterKindList()
size_t idx = 0; size_t idx = 0;
const char *kind; const char *kind;
while(obs_enum_filter_types(idx++, &kind)) while (obs_enum_filter_types(idx++, &kind))
ret.push_back(kind); ret.push_back(kind);
return ret; return ret;
} }
std::vector<json> Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *source) std::vector<json>
Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *source)
{ {
std::vector<json> filters; std::vector<json> filters;
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) { auto enumFilters = [](obs_source_t *, obs_source_t *filter,
auto filters = reinterpret_cast<std::vector<json>*>(param); void *param) {
auto filters = reinterpret_cast<std::vector<json> *>(param);
json filterJson; json filterJson;
filterJson["filterEnabled"] = obs_source_enabled(filter); filterJson["filterEnabled"] = obs_source_enabled(filter);
@ -306,8 +336,10 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *sou
filterJson["filterKind"] = obs_source_get_id(filter); filterJson["filterKind"] = obs_source_get_id(filter);
filterJson["filterName"] = obs_source_get_name(filter); filterJson["filterName"] = obs_source_get_name(filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(filter); OBSDataAutoRelease filterSettings =
filterJson["filterSettings"] = Utils::Json::ObsDataToJson(filterSettings); obs_source_get_settings(filter);
filterJson["filterSettings"] =
Utils::Json::ObsDataToJson(filterSettings);
filters->push_back(filterJson); filters->push_back(filterJson);
}; };

View File

@ -19,9 +19,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Obs.h" #include "Obs.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
#define RET_COMPARE(str, x) if (str == #x) return x; #define RET_COMPARE(str, x) \
if (str == #x) \
return x;
enum obs_bounds_type Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string boundsType) enum obs_bounds_type
Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string boundsType)
{ {
RET_COMPARE(boundsType, OBS_BOUNDS_NONE) RET_COMPARE(boundsType, OBS_BOUNDS_NONE)
RET_COMPARE(boundsType, OBS_BOUNDS_STRETCH) RET_COMPARE(boundsType, OBS_BOUNDS_STRETCH)
@ -34,7 +37,8 @@ enum obs_bounds_type Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string
return OBS_BOUNDS_NONE; return OBS_BOUNDS_NONE;
} }
enum ObsMediaInputAction Utils::Obs::EnumHelper::GetMediaInputAction(std::string mediaAction) enum ObsMediaInputAction
Utils::Obs::EnumHelper::GetMediaInputAction(std::string mediaAction)
{ {
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY) RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE) RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE)
@ -46,7 +50,8 @@ enum ObsMediaInputAction Utils::Obs::EnumHelper::GetMediaInputAction(std::string
return OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE; return OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE;
} }
enum obs_blending_type Utils::Obs::EnumHelper::GetSceneItemBlendMode(std::string mode) enum obs_blending_type
Utils::Obs::EnumHelper::GetSceneItemBlendMode(std::string mode)
{ {
RET_COMPARE(mode, OBS_BLEND_NORMAL) RET_COMPARE(mode, OBS_BLEND_NORMAL)
RET_COMPARE(mode, OBS_BLEND_ADDITIVE) RET_COMPARE(mode, OBS_BLEND_ADDITIVE)

View File

@ -28,7 +28,7 @@ uint64_t Utils::Obs::NumberHelper::GetOutputDuration(obs_output_t *output)
if (!output || !obs_output_active(output)) if (!output || !obs_output_active(output))
return 0; return 0;
video_t* video = obs_output_video(output); video_t *video = obs_output_video(output);
uint64_t frameTimeNs = video_output_get_frame_time(video); uint64_t frameTimeNs = video_output_get_frame_time(video);
int totalFrames = obs_output_get_total_frames(output); int totalFrames = obs_output_get_total_frames(output);
@ -39,7 +39,7 @@ size_t Utils::Obs::NumberHelper::GetSceneCount()
{ {
size_t ret; size_t ret;
auto sceneEnumProc = [](void *param, obs_source_t *scene) { auto sceneEnumProc = [](void *param, obs_source_t *scene) {
auto ret = static_cast<size_t*>(param); auto ret = static_cast<size_t *>(param);
if (obs_source_is_group(scene)) if (obs_source_is_group(scene))
return true; return true;
@ -53,7 +53,8 @@ size_t Utils::Obs::NumberHelper::GetSceneCount()
return ret; return ret;
} }
size_t Utils::Obs::NumberHelper::GetSourceFilterIndex(obs_source_t *source, obs_source_t *filter) size_t Utils::Obs::NumberHelper::GetSourceFilterIndex(obs_source_t *source,
obs_source_t *filter)
{ {
struct FilterSearch { struct FilterSearch {
obs_source_t *filter; obs_source_t *filter;
@ -61,8 +62,9 @@ size_t Utils::Obs::NumberHelper::GetSourceFilterIndex(obs_source_t *source, obs_
size_t index; size_t index;
}; };
auto search = [](obs_source_t *, obs_source_t *filter, void *priv_data) { auto search = [](obs_source_t *, obs_source_t *filter,
auto filterSearch = static_cast<FilterSearch*>(priv_data); void *priv_data) {
auto filterSearch = static_cast<FilterSearch *>(priv_data);
if (filter == filterSearch->filter) if (filter == filterSearch->filter)
filterSearch->found = true; filterSearch->found = true;

View File

@ -27,15 +27,20 @@ json Utils::Obs::ObjectHelper::GetStats()
{ {
json ret; json ret;
std::string outputPath = Utils::Obs::StringHelper::GetCurrentRecordOutputPath(); std::string outputPath =
Utils::Obs::StringHelper::GetCurrentRecordOutputPath();
video_t* video = obs_get_video(); video_t *video = obs_get_video();
ret["cpuUsage"] = os_cpu_usage_info_query(GetCpuUsageInfo()); ret["cpuUsage"] = os_cpu_usage_info_query(GetCpuUsageInfo());
ret["memoryUsage"] = (double)os_get_proc_resident_size() / (1024.0 * 1024.0); ret["memoryUsage"] =
ret["availableDiskSpace"] = (double)os_get_free_disk_space(outputPath.c_str()) / (1024.0 * 1024.0); (double)os_get_proc_resident_size() / (1024.0 * 1024.0);
ret["availableDiskSpace"] =
(double)os_get_free_disk_space(outputPath.c_str()) /
(1024.0 * 1024.0);
ret["activeFps"] = obs_get_active_fps(); ret["activeFps"] = obs_get_active_fps();
ret["averageFrameRenderTime"] = (double)obs_get_average_frame_time_ns() / 1000000.0; ret["averageFrameRenderTime"] =
(double)obs_get_average_frame_time_ns() / 1000000.0;
ret["renderSkippedFrames"] = obs_get_lagged_frames(); ret["renderSkippedFrames"] = obs_get_lagged_frames();
ret["renderTotalFrames"] = obs_get_total_frames(); ret["renderTotalFrames"] = obs_get_total_frames();
ret["outputSkippedFrames"] = video_output_get_skipped_frames(video); ret["outputSkippedFrames"] = video_output_get_skipped_frames(video);
@ -73,7 +78,8 @@ json Utils::Obs::ObjectHelper::GetSceneItemTransform(obs_sceneitem_t *item)
ret["alignment"] = osi.alignment; ret["alignment"] = osi.alignment;
ret["boundsType"] = StringHelper::GetSceneItemBoundsType(osi.bounds_type); ret["boundsType"] =
StringHelper::GetSceneItemBoundsType(osi.bounds_type);
ret["boundsAlignment"] = osi.bounds_alignment; ret["boundsAlignment"] = osi.bounds_alignment;
ret["boundsWidth"] = osi.bounds.x; ret["boundsWidth"] = osi.bounds.x;
ret["boundsHeight"] = osi.bounds.y; ret["boundsHeight"] = osi.bounds.y;

View File

@ -35,7 +35,8 @@ obs_hotkey_t *Utils::Obs::SearchHelper::GetHotkeyByName(std::string name)
} }
// Increments source ref. Use OBSSourceAutoRelease // Increments source ref. Use OBSSourceAutoRelease
obs_source_t *Utils::Obs::SearchHelper::GetSceneTransitionByName(std::string name) obs_source_t *
Utils::Obs::SearchHelper::GetSceneTransitionByName(std::string name)
{ {
obs_frontend_source_list transitionList = {}; obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList); obs_frontend_get_transitions(&transitionList);
@ -61,7 +62,9 @@ struct SceneItemSearchData {
}; };
// Increments item ref. Use OBSSceneItemAutoRelease // Increments item ref. Use OBSSceneItemAutoRelease
obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene, std::string name, int offset) obs_sceneitem_t *
Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene,
std::string name, int offset)
{ {
if (name.empty()) if (name.empty())
return nullptr; return nullptr;
@ -70,26 +73,34 @@ obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene
enumData.name = name; enumData.name = name;
enumData.offset = offset; enumData.offset = offset;
obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) { obs_scene_enum_items(
auto enumData = static_cast<SceneItemSearchData*>(param); scene,
[](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
auto enumData =
static_cast<SceneItemSearchData *>(param);
OBSSource itemSource = obs_sceneitem_get_source(sceneItem); OBSSource itemSource =
std::string sourceName = obs_source_get_name(itemSource); obs_sceneitem_get_source(sceneItem);
if (sourceName == enumData->name) { std::string sourceName =
if (enumData->offset > 0) { obs_source_get_name(itemSource);
enumData->offset--; if (sourceName == enumData->name) {
} else { if (enumData->offset > 0) {
if (enumData->ret) // Release existing selection in the case of last match selection enumData->offset--;
obs_sceneitem_release(enumData->ret); } else {
obs_sceneitem_addref(sceneItem); if (enumData->ret) // Release existing selection in the case of last match selection
enumData->ret = sceneItem; obs_sceneitem_release(
if (enumData->offset == 0) // Only break if in normal selection mode (not offset == -1) enumData->ret);
return false; obs_sceneitem_addref(sceneItem);
enumData->ret = sceneItem;
if (enumData->offset ==
0) // Only break if in normal selection mode (not offset == -1)
return false;
}
} }
}
return true; return true;
}, &enumData); },
&enumData);
return enumData.ret; return enumData.ret;
} }

View File

@ -23,7 +23,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Obs.h" #include "Obs.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
#define CASE(x) case x: return #x; #define CASE(x) \
case x: \
return #x;
std::string Utils::Obs::StringHelper::GetObsVersion() std::string Utils::Obs::StringHelper::GetObsVersion()
{ {
@ -75,7 +77,7 @@ std::string Utils::Obs::StringHelper::GetSourceType(obs_source_t *source)
obs_source_type sourceType = obs_source_get_type(source); obs_source_type sourceType = obs_source_get_type(source);
switch (sourceType) { switch (sourceType) {
default: default:
CASE(OBS_SOURCE_TYPE_INPUT) CASE(OBS_SOURCE_TYPE_INPUT)
CASE(OBS_SOURCE_TYPE_FILTER) CASE(OBS_SOURCE_TYPE_FILTER)
CASE(OBS_SOURCE_TYPE_TRANSITION) CASE(OBS_SOURCE_TYPE_TRANSITION)
@ -83,10 +85,11 @@ std::string Utils::Obs::StringHelper::GetSourceType(obs_source_t *source)
} }
} }
std::string Utils::Obs::StringHelper::GetInputMonitorType(enum obs_monitoring_type monitorType) std::string Utils::Obs::StringHelper::GetInputMonitorType(
enum obs_monitoring_type monitorType)
{ {
switch (monitorType) { switch (monitorType) {
default: default:
CASE(OBS_MONITORING_TYPE_NONE) CASE(OBS_MONITORING_TYPE_NONE)
CASE(OBS_MONITORING_TYPE_MONITOR_ONLY) CASE(OBS_MONITORING_TYPE_MONITOR_ONLY)
CASE(OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT) CASE(OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT)
@ -105,7 +108,7 @@ std::string Utils::Obs::StringHelper::GetMediaInputState(obs_source_t *input)
obs_media_state mediaState = obs_source_media_get_state(input); obs_media_state mediaState = obs_source_media_get_state(input);
switch (mediaState) { switch (mediaState) {
default: default:
CASE(OBS_MEDIA_STATE_NONE) CASE(OBS_MEDIA_STATE_NONE)
CASE(OBS_MEDIA_STATE_PLAYING) CASE(OBS_MEDIA_STATE_PLAYING)
CASE(OBS_MEDIA_STATE_OPENING) CASE(OBS_MEDIA_STATE_OPENING)
@ -135,10 +138,11 @@ std::string Utils::Obs::StringHelper::GetLastReplayBufferFilePath()
return savedReplayPath; return savedReplayPath;
} }
std::string Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_type type) std::string
Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_type type)
{ {
switch (type) { switch (type) {
default: default:
CASE(OBS_BOUNDS_NONE) CASE(OBS_BOUNDS_NONE)
CASE(OBS_BOUNDS_STRETCH) CASE(OBS_BOUNDS_STRETCH)
CASE(OBS_BOUNDS_SCALE_INNER) CASE(OBS_BOUNDS_SCALE_INNER)
@ -149,10 +153,11 @@ std::string Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_typ
} }
} }
std::string Utils::Obs::StringHelper::GetSceneItemBlendMode(enum obs_blending_type mode) std::string
Utils::Obs::StringHelper::GetSceneItemBlendMode(enum obs_blending_type mode)
{ {
switch (mode) { switch (mode) {
default: default:
CASE(OBS_BLEND_NORMAL) CASE(OBS_BLEND_NORMAL)
CASE(OBS_BLEND_ADDITIVE) CASE(OBS_BLEND_ADDITIVE)
CASE(OBS_BLEND_SUBTRACT) CASE(OBS_BLEND_SUBTRACT)
@ -173,14 +178,17 @@ std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
uint64_t secsPart = secs % 60ULL; uint64_t secsPart = secs % 60ULL;
uint64_t msPart = ms % 1000ULL; uint64_t msPart = ms % 1000ULL;
QString formatted = QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart); QString formatted = QString::asprintf("%02" PRIu64 ":%02" PRIu64
":%02" PRIu64 ".%03" PRIu64,
hoursPart, minutesPart, secsPart,
msPart);
return formatted.toStdString(); return formatted.toStdString();
} }
std::string Utils::Obs::StringHelper::GetOutputState(ObsOutputState state) std::string Utils::Obs::StringHelper::GetOutputState(ObsOutputState state)
{ {
switch (state) { switch (state) {
default: default:
CASE(OBS_WEBSOCKET_OUTPUT_UNKNOWN) CASE(OBS_WEBSOCKET_OUTPUT_UNKNOWN)
CASE(OBS_WEBSOCKET_OUTPUT_STARTING) CASE(OBS_WEBSOCKET_OUTPUT_STARTING)
CASE(OBS_WEBSOCKET_OUTPUT_STARTED) CASE(OBS_WEBSOCKET_OUTPUT_STARTED)

View File

@ -26,35 +26,43 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Obs_VolumeMeter_Helpers.h" #include "Obs_VolumeMeter_Helpers.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) : Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input)
PeakMeterType(SAMPLE_PEAK_METER), : PeakMeterType(SAMPLE_PEAK_METER),
_input(obs_source_get_weak_source(input)), _input(obs_source_get_weak_source(input)),
_channels(0), _channels(0),
_lastUpdate(0), _lastUpdate(0),
_volume(obs_source_get_volume(input)) _volume(obs_source_get_volume(input))
{ {
signal_handler_t *sh = obs_source_get_signal_handler(input); signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this); signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this); obs_source_add_audio_capture_callback(
input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input)); blog_debug(
"[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s",
obs_source_get_name(input));
} }
Utils::Obs::VolumeMeter::Meter::~Meter() Utils::Obs::VolumeMeter::Meter::~Meter()
{ {
OBSSourceAutoRelease input = obs_weak_source_get_source(_input); OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) { if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?"); blog(LOG_WARNING,
"[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
return; return;
} }
signal_handler_t *sh = obs_source_get_signal_handler(input); signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this); signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback,
this);
obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this); obs_source_remove_audio_capture_callback(
input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input)); blog_debug(
"[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s",
obs_source_get_name(input));
} }
bool Utils::Obs::VolumeMeter::Meter::InputValid() bool Utils::Obs::VolumeMeter::Meter::InputValid()
@ -68,7 +76,8 @@ json Utils::Obs::VolumeMeter::Meter::GetMeterData()
OBSSourceAutoRelease input = obs_weak_source_get_source(_input); OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) { if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?"); blog(LOG_WARNING,
"[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
return ret; return ret;
} }
@ -77,7 +86,8 @@ json Utils::Obs::VolumeMeter::Meter::GetMeterData()
std::unique_lock<std::mutex> l(_mutex); std::unique_lock<std::mutex> l(_mutex);
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3) if (_lastUpdate != 0 &&
(os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
ResetAudioLevels(); ResetAudioLevels();
for (int channel = 0; channel < _channels; channel++) { for (int channel = 0; channel < _channels; channel++) {
@ -100,14 +110,16 @@ json Utils::Obs::VolumeMeter::Meter::GetMeterData()
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels() void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
{ {
_lastUpdate = 0; _lastUpdate = 0;
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) { for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS;
channelNumber++) {
_magnitude[channelNumber] = 0; _magnitude[channelNumber] = 0;
_peak[channelNumber] = 0; _peak[channelNumber] = 0;
} }
} }
// MUST HOLD LOCK // MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data) void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(
const struct audio_data *data)
{ {
int channels = 0; int channels = 0;
for (int i = 0; i < MAX_AV_PLANES; i++) { for (int i = 0; i < MAX_AV_PLANES; i++) {
@ -129,7 +141,7 @@ void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
int channelNumber = 0; int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber]; float *samples = (float *)data->data[planeNumber];
if (!samples) if (!samples)
continue; continue;
@ -139,45 +151,64 @@ void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
continue; continue;
} }
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]); __m128 previousSamples =
_mm_loadu_ps(_previousSamples[channelNumber]);
float peak; float peak;
switch (PeakMeterType) { switch (PeakMeterType) {
default: default:
case SAMPLE_PEAK_METER: case SAMPLE_PEAK_METER:
peak = GetSamplePeak(previousSamples, samples, sampleCount); peak = GetSamplePeak(previousSamples, samples,
break; sampleCount);
case TRUE_PEAK_METER: break;
peak = GetTruePeak(previousSamples, samples, sampleCount); case TRUE_PEAK_METER:
break; peak = GetTruePeak(previousSamples, samples,
sampleCount);
break;
} }
switch (sampleCount) { switch (sampleCount) {
case 0: case 0:
break; break;
case 1: case 1:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1]; _previousSamples[channelNumber][0] =
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][1];
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][1] =
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][2];
break; _previousSamples[channelNumber][2] =
case 2: _previousSamples[channelNumber][3];
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][3] =
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3]; samples[sampleCount - 1];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; break;
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; case 2:
break; _previousSamples[channelNumber][0] =
case 3: _previousSamples[channelNumber][2];
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][1] =
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][2] =
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; samples[sampleCount - 2];
break; _previousSamples[channelNumber][3] =
default: samples[sampleCount - 1];
_previousSamples[channelNumber][0] = samples[sampleCount - 4]; break;
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; case 3:
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][0] =
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3];
_previousSamples[channelNumber][1] =
samples[sampleCount - 3];
_previousSamples[channelNumber][2] =
samples[sampleCount - 2];
_previousSamples[channelNumber][3] =
samples[sampleCount - 1];
break;
default:
_previousSamples[channelNumber][0] =
samples[sampleCount - 4];
_previousSamples[channelNumber][1] =
samples[sampleCount - 3];
_previousSamples[channelNumber][2] =
samples[sampleCount - 2];
_previousSamples[channelNumber][3] =
samples[sampleCount - 1];
} }
_peak[channelNumber] = peak; _peak[channelNumber] = peak;
@ -190,13 +221,14 @@ void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
} }
// MUST HOLD LOCK // MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data) void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(
const struct audio_data *data)
{ {
size_t sampleCount = data->frames; size_t sampleCount = data->frames;
int channelNumber = 0; int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber]; float *samples = (float *)data->data[planeNumber];
if (!samples) if (!samples)
continue; continue;
@ -212,9 +244,11 @@ void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *d
} }
} }
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted) void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(
void *priv_data, obs_source_t *, const struct audio_data *data,
bool muted)
{ {
auto c = static_cast<Meter*>(priv_data); auto c = static_cast<Meter *>(priv_data);
std::unique_lock<std::mutex> l(c->_mutex); std::unique_lock<std::mutex> l(c->_mutex);
@ -226,24 +260,24 @@ void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data,
c->_lastUpdate = os_gettime_ns(); c->_lastUpdate = os_gettime_ns();
} }
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd) void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data,
calldata_t *cd)
{ {
auto c = static_cast<Meter*>(priv_data); auto c = static_cast<Meter *>(priv_data);
c->_volume = (float)calldata_float(cd, "volume"); c->_volume = (float)calldata_float(cd, "volume");
} }
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) : Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb,
_updateCallback(cb), uint64_t updatePeriod)
_updatePeriod(updatePeriod), : _updateCallback(cb), _updatePeriod(updatePeriod), _running(false)
_running(false)
{ {
signal_handler_t *sh = obs_get_signal_handler(); signal_handler_t *sh = obs_get_signal_handler();
if (!sh) if (!sh)
return; return;
auto enumProc = [](void *priv_data, obs_source_t *input) { auto enumProc = [](void *priv_data, obs_source_t *input) {
auto c = static_cast<Handler*>(priv_data); auto c = static_cast<Handler *>(priv_data);
if (!obs_source_active(input)) if (!obs_source_active(input))
return true; return true;
@ -258,13 +292,16 @@ Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeri
}; };
obs_enum_sources(enumProc, this); obs_enum_sources(enumProc, this);
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this); signal_handler_connect(sh, "source_activate",
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this); Handler::InputActivateCallback, this);
signal_handler_connect(sh, "source_deactivate",
Handler::InputDeactivateCallback, this);
_running = true; _running = true;
_updateThread = std::thread(&Handler::UpdateThread, this); _updateThread = std::thread(&Handler::UpdateThread, this);
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created."); blog_debug(
"[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
} }
Utils::Obs::VolumeMeter::Handler::~Handler() Utils::Obs::VolumeMeter::Handler::~Handler()
@ -273,8 +310,10 @@ Utils::Obs::VolumeMeter::Handler::~Handler()
if (!sh) if (!sh)
return; return;
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this); signal_handler_disconnect(sh, "source_activate",
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this); Handler::InputActivateCallback, this);
signal_handler_disconnect(sh, "source_deactivate",
Handler::InputDeactivateCallback, this);
if (_running) { if (_running) {
_running = false; _running = false;
@ -284,16 +323,20 @@ Utils::Obs::VolumeMeter::Handler::~Handler()
if (_updateThread.joinable()) if (_updateThread.joinable())
_updateThread.join(); _updateThread.join();
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed."); blog_debug(
"[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
} }
void Utils::Obs::VolumeMeter::Handler::UpdateThread() void Utils::Obs::VolumeMeter::Handler::UpdateThread()
{ {
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started."); blog_debug(
"[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
while (_running) { while (_running) {
{ {
std::unique_lock<std::mutex> l(_mutex); std::unique_lock<std::mutex> l(_mutex);
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; })) if (_cond.wait_for(
l, std::chrono::milliseconds(_updatePeriod),
[this] { return !_running; }))
break; break;
} }
@ -308,12 +351,14 @@ void Utils::Obs::VolumeMeter::Handler::UpdateThread()
if (_updateCallback) if (_updateCallback)
_updateCallback(inputs); _updateCallback(inputs);
} }
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped."); blog_debug(
"[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
} }
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd) void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data,
calldata_t *cd)
{ {
auto c = static_cast<Handler*>(priv_data); auto c = static_cast<Handler *>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source"); obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input) if (!input)
@ -330,9 +375,10 @@ void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, ca
c->_meters.emplace_back(std::move(new Meter(input))); c->_meters.emplace_back(std::move(new Meter(input)));
} }
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd) void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data,
calldata_t *cd)
{ {
auto c = static_cast<Handler*>(priv_data); auto c = static_cast<Handler *>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source"); obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input) if (!input)
@ -345,7 +391,8 @@ void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data,
std::unique_lock<std::mutex> l(c->_meterMutex); std::unique_lock<std::mutex> l(c->_meterMutex);
std::vector<MeterPtr>::iterator iter; std::vector<MeterPtr>::iterator iter;
for (iter = c->_meters.begin(); iter != c->_meters.end();) { for (iter = c->_meters.begin(); iter != c->_meters.end();) {
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input)) if (obs_weak_source_references_source(
iter->get()->GetWeakInput(), input))
iter = c->_meters.erase(iter); iter = c->_meters.erase(iter);
else else
++iter; ++iter;

View File

@ -31,69 +31,72 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Json.h" #include "Json.h"
namespace Utils { namespace Utils {
namespace Obs { namespace Obs {
namespace VolumeMeter { namespace VolumeMeter {
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c // Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c
// Keeps a running tally of the current audio levels, for a specific input // Keeps a running tally of the current audio levels, for a specific input
class Meter { class Meter {
public: public:
Meter(obs_source_t *input); Meter(obs_source_t *input);
~Meter(); ~Meter();
bool InputValid(); bool InputValid();
obs_weak_source_t *GetWeakInput() { return _input; } obs_weak_source_t *GetWeakInput() { return _input; }
json GetMeterData(); json GetMeterData();
std::atomic<enum obs_peak_meter_type> PeakMeterType; std::atomic<enum obs_peak_meter_type> PeakMeterType;
private: private:
OBSWeakSourceAutoRelease _input; OBSWeakSourceAutoRelease _input;
// All values in mul // All values in mul
std::mutex _mutex; std::mutex _mutex;
bool _muted; bool _muted;
int _channels; int _channels;
float _magnitude[MAX_AUDIO_CHANNELS]; float _magnitude[MAX_AUDIO_CHANNELS];
float _peak[MAX_AUDIO_CHANNELS]; float _peak[MAX_AUDIO_CHANNELS];
float _previousSamples[MAX_AUDIO_CHANNELS][4]; float _previousSamples[MAX_AUDIO_CHANNELS][4];
std::atomic<uint64_t> _lastUpdate; std::atomic<uint64_t> _lastUpdate;
std::atomic<float> _volume; std::atomic<float> _volume;
void ResetAudioLevels(); void ResetAudioLevels();
void ProcessAudioChannels(const struct audio_data *data); void ProcessAudioChannels(const struct audio_data *data);
void ProcessPeak(const struct audio_data *data); void ProcessPeak(const struct audio_data *data);
void ProcessMagnitude(const struct audio_data *data); void ProcessMagnitude(const struct audio_data *data);
static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted); static void InputAudioCaptureCallback(void *priv_data,
static void InputVolumeCallback(void *priv_data, calldata_t *cd); obs_source_t *source,
}; const struct audio_data *data,
bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd);
};
// Maintains an array of active inputs // Maintains an array of active inputs
class Handler { class Handler {
typedef std::function<void(std::vector<json>)> UpdateCallback; typedef std::function<void(std::vector<json>)> UpdateCallback;
typedef std::unique_ptr<Meter> MeterPtr; typedef std::unique_ptr<Meter> MeterPtr;
public: public:
Handler(UpdateCallback cb, uint64_t updatePeriod = 50); Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
~Handler(); ~Handler();
private: private:
UpdateCallback _updateCallback; UpdateCallback _updateCallback;
std::mutex _meterMutex; std::mutex _meterMutex;
std::vector<MeterPtr> _meters; std::vector<MeterPtr> _meters;
uint64_t _updatePeriod; uint64_t _updatePeriod;
std::mutex _mutex; std::mutex _mutex;
std::condition_variable _cond; std::condition_variable _cond;
std::atomic<bool> _running; std::atomic<bool> _running;
std::thread _updateThread; std::thread _updateThread;
void UpdateThread(); void UpdateThread();
static void InputActivateCallback(void *priv_data, calldata_t *cd); static void InputActivateCallback(void *priv_data, calldata_t *cd);
static void InputDeactivateCallback(void *priv_data, calldata_t *cd); static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
}; };
} }
} }
} }

View File

@ -56,7 +56,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
r = fmaxf(r, x4_mem[3]); \ r = fmaxf(r, x4_mem[3]); \
} while (false) } while (false)
static float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount) static float GetSamplePeak(__m128 previousSamples, const float *samples,
size_t sampleCount)
{ {
__m128 peak = previousSamples; __m128 peak = previousSamples;
for (size_t i = 0; (i + 3) < sampleCount; i += 4) { for (size_t i = 0; (i + 3) < sampleCount; i += 4) {
@ -69,12 +70,17 @@ static float GetSamplePeak(__m128 previousSamples, const float *samples, size_t
return ret; return ret;
} }
static float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount) static float GetTruePeak(__m128 previousSamples, const float *samples,
size_t sampleCount)
{ {
const __m128 m3 = _mm_set_ps(-0.155915f, 0.935489f, 0.233872f, -0.103943f); const __m128 m3 =
const __m128 m1 = _mm_set_ps(-0.216236f, 0.756827f, 0.504551f, -0.189207f); _mm_set_ps(-0.155915f, 0.935489f, 0.233872f, -0.103943f);
const __m128 p1 = _mm_set_ps(-0.189207f, 0.504551f, 0.756827f, -0.216236f); const __m128 m1 =
const __m128 p3 = _mm_set_ps(-0.103943f, 0.233872f, 0.935489f, -0.155915f); _mm_set_ps(-0.216236f, 0.756827f, 0.504551f, -0.189207f);
const __m128 p1 =
_mm_set_ps(-0.189207f, 0.504551f, 0.756827f, -0.216236f);
const __m128 p3 =
_mm_set_ps(-0.103943f, 0.233872f, 0.935489f, -0.155915f);
__m128 work = previousSamples; __m128 work = previousSamples;
__m128 peak = previousSamples; __m128 peak = previousSamples;

View File

@ -53,24 +53,36 @@ std::string Utils::Platform::GetLocalAddress()
std::vector<std::pair<QString, uint8_t>> preferredAddresses; std::vector<std::pair<QString, uint8_t>> preferredAddresses;
for (auto address : validAddresses) { for (auto address : validAddresses) {
// Attribute a priority (0 is best) to the address to choose the best picks // Attribute a priority (0 is best) to the address to choose the best picks
if (address.startsWith("192.168.1.") || address.startsWith("192.168.0.")) { // Prefer common consumer router network prefixes if (address.startsWith("192.168.1.") ||
address.startsWith(
"192.168.0.")) { // Prefer common consumer router network prefixes
if (address.startsWith("192.168.56.")) if (address.startsWith("192.168.56."))
preferredAddresses.push_back(std::make_pair(address, 255)); // Ignore virtualbox default preferredAddresses.push_back(std::make_pair(
address,
255)); // Ignore virtualbox default
else else
preferredAddresses.push_back(std::make_pair(address, 0)); preferredAddresses.push_back(
} else if (address.startsWith("172.16.")) { // Slightly less common consumer router network prefixes std::make_pair(address, 0));
preferredAddresses.push_back(std::make_pair(address, 1)); } else if (address.startsWith(
} else if (address.startsWith("10.")) { // Even less common consumer router network prefixes "172.16.")) { // Slightly less common consumer router network prefixes
preferredAddresses.push_back(std::make_pair(address, 2)); preferredAddresses.push_back(
std::make_pair(address, 1));
} else if (address.startsWith(
"10.")) { // Even less common consumer router network prefixes
preferredAddresses.push_back(
std::make_pair(address, 2));
} else { // Set all other addresses to equal priority } else { // Set all other addresses to equal priority
preferredAddresses.push_back(std::make_pair(address, 255)); preferredAddresses.push_back(
std::make_pair(address, 255));
} }
} }
// Sort by priority // Sort by priority
std::sort(preferredAddresses.begin(), preferredAddresses.end(), [=](std::pair<QString, uint8_t> a, std::pair<QString, uint8_t> b) { std::sort(preferredAddresses.begin(), preferredAddresses.end(),
return a.second < b.second; [=](std::pair<QString, uint8_t> a,
}); std::pair<QString, uint8_t> b) {
return a.second < b.second;
});
// Return highest priority address // Return highest priority address
return preferredAddresses[0].first.toStdString(); return preferredAddresses[0].first.toStdString();
@ -120,24 +132,35 @@ struct SystemTrayNotification {
QString body; QString body;
}; };
void Utils::Platform::SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QString title, QString body) void Utils::Platform::SendTrayNotification(QSystemTrayIcon::MessageIcon icon,
QString title, QString body)
{ {
if (!QSystemTrayIcon::isSystemTrayAvailable() || !QSystemTrayIcon::supportsMessages()) if (!QSystemTrayIcon::isSystemTrayAvailable() ||
!QSystemTrayIcon::supportsMessages())
return; return;
SystemTrayNotification *notification = new SystemTrayNotification{icon, title, body}; SystemTrayNotification *notification =
new SystemTrayNotification{icon, title, body};
obs_queue_task(OBS_TASK_UI, [](void* param) { obs_queue_task(
void *systemTrayPtr = obs_frontend_get_system_tray(); OBS_TASK_UI,
auto systemTray = static_cast<QSystemTrayIcon*>(systemTrayPtr); [](void *param) {
void *systemTrayPtr = obs_frontend_get_system_tray();
auto systemTray =
static_cast<QSystemTrayIcon *>(systemTrayPtr);
auto notification = static_cast<SystemTrayNotification*>(param); auto notification =
systemTray->showMessage(notification->title, notification->body, notification->icon); static_cast<SystemTrayNotification *>(param);
delete notification; systemTray->showMessage(notification->title,
}, (void*)notification, false); notification->body,
notification->icon);
delete notification;
},
(void *)notification, false);
} }
bool Utils::Platform::GetTextFileContent(std::string fileName, std::string &content) bool Utils::Platform::GetTextFileContent(std::string fileName,
std::string &content)
{ {
QFile f(QString::fromStdString(fileName)); QFile f(QString::fromStdString(fileName));
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) if (!f.open(QIODevice::ReadOnly | QIODevice::Text))
@ -147,7 +170,8 @@ bool Utils::Platform::GetTextFileContent(std::string fileName, std::string &cont
return true; return true;
} }
bool Utils::Platform::SetTextFileContent(std::string fileName, std::string content, bool createNew) bool Utils::Platform::SetTextFileContent(std::string fileName,
std::string content, bool createNew)
{ {
if (!createNew && !QFile::exists(QString::fromStdString(fileName))) if (!createNew && !QFile::exists(QString::fromStdString(fileName)))
return false; return false;

View File

@ -24,13 +24,15 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
namespace Utils { namespace Utils {
namespace Platform { namespace Platform {
std::string GetLocalAddress(); std::string GetLocalAddress();
std::string GetLoopbackAddress(bool allowIpv6 = true); std::string GetLoopbackAddress(bool allowIpv6 = true);
QString GetCommandLineArgument(QString arg); QString GetCommandLineArgument(QString arg);
bool GetCommandLineFlagSet(QString arg); bool GetCommandLineFlagSet(QString arg);
void SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QString title, QString body); void SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QString title,
bool GetTextFileContent(std::string fileName, std::string &content); QString body);
bool SetTextFileContent(std::string filePath, std::string content, bool createNew = true); bool GetTextFileContent(std::string fileName, std::string &content);
} bool SetTextFileContent(std::string filePath, std::string content,
bool createNew = true);
}
} }

View File

@ -31,9 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../utils/Platform.h" #include "../utils/Platform.h"
#include "../utils/Compat.h" #include "../utils/Compat.h"
WebSocketServer::WebSocketServer() : WebSocketServer::WebSocketServer() : QObject(nullptr), _sessions()
QObject(nullptr),
_sessions()
{ {
_server.get_alog().clear_channels(websocketpp::log::alevel::all); _server.get_alog().clear_channels(websocketpp::log::alevel::all);
_server.get_elog().clear_channels(websocketpp::log::elevel::all); _server.get_elog().clear_channels(websocketpp::log::elevel::all);
@ -44,38 +42,27 @@ WebSocketServer::WebSocketServer() :
#endif #endif
_server.set_validate_handler( _server.set_validate_handler(
websocketpp::lib::bind( websocketpp::lib::bind(&WebSocketServer::onValidate, this,
&WebSocketServer::onValidate, this, websocketpp::lib::placeholders::_1 websocketpp::lib::placeholders::_1));
)
);
_server.set_open_handler( _server.set_open_handler(
websocketpp::lib::bind( websocketpp::lib::bind(&WebSocketServer::onOpen, this,
&WebSocketServer::onOpen, this, websocketpp::lib::placeholders::_1 websocketpp::lib::placeholders::_1));
)
);
_server.set_close_handler( _server.set_close_handler(
websocketpp::lib::bind( websocketpp::lib::bind(&WebSocketServer::onClose, this,
&WebSocketServer::onClose, this, websocketpp::lib::placeholders::_1 websocketpp::lib::placeholders::_1));
)
);
_server.set_message_handler( _server.set_message_handler(
websocketpp::lib::bind( websocketpp::lib::bind(&WebSocketServer::onMessage, this,
&WebSocketServer::onMessage, this, websocketpp::lib::placeholders::_1, websocketpp::lib::placeholders::_2 websocketpp::lib::placeholders::_1,
) websocketpp::lib::placeholders::_2));
);
auto eventHandler = GetEventHandler(); auto eventHandler = GetEventHandler();
eventHandler->SetBroadcastCallback( eventHandler->SetBroadcastCallback(
std::bind( std::bind(&WebSocketServer::BroadcastEvent, this,
&WebSocketServer::BroadcastEvent, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4 std::placeholders::_1, std::placeholders::_2,
) std::placeholders::_3, std::placeholders::_4));
);
eventHandler->SetObsLoadedCallback( eventHandler->SetObsLoadedCallback(
std::bind( std::bind(&WebSocketServer::onObsLoaded, this));
&WebSocketServer::onObsLoaded, this
)
);
} }
WebSocketServer::~WebSocketServer() WebSocketServer::~WebSocketServer()
@ -89,12 +76,17 @@ void WebSocketServer::ServerRunner()
blog(LOG_INFO, "[WebSocketServer::ServerRunner] IO thread started."); blog(LOG_INFO, "[WebSocketServer::ServerRunner] IO thread started.");
try { try {
_server.run(); _server.run();
} catch (websocketpp::exception const & e) { } catch (websocketpp::exception const &e) {
blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s", e.what()); blog(LOG_ERROR,
} catch (const std::exception & e) { "[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s",
blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s", e.what()); e.what());
} catch (const std::exception &e) {
blog(LOG_ERROR,
"[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s",
e.what());
} catch (...) { } catch (...) {
blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error"); blog(LOG_ERROR,
"[WebSocketServer::ServerRunner] websocketpp instance returned an error");
} }
blog(LOG_INFO, "[WebSocketServer::ServerRunner] IO thread exited."); blog(LOG_INFO, "[WebSocketServer::ServerRunner] IO thread exited.");
} }
@ -102,52 +94,70 @@ void WebSocketServer::ServerRunner()
void WebSocketServer::Start() void WebSocketServer::Start()
{ {
if (_server.is_listening()) { if (_server.is_listening()) {
blog(LOG_WARNING, "[WebSocketServer::Start] Call to Start() but the server is already listening."); blog(LOG_WARNING,
"[WebSocketServer::Start] Call to Start() but the server is already listening.");
return; return;
} }
auto conf = GetConfig(); auto conf = GetConfig();
if (!conf) { if (!conf) {
blog(LOG_ERROR, "[WebSocketServer::Start] Unable to retreive config!"); blog(LOG_ERROR,
"[WebSocketServer::Start] Unable to retreive config!");
return; return;
} }
_authenticationSalt = Utils::Crypto::GenerateSalt(); _authenticationSalt = Utils::Crypto::GenerateSalt();
_authenticationSecret = Utils::Crypto::GenerateSecret(conf->ServerPassword.toStdString(), _authenticationSalt); _authenticationSecret = Utils::Crypto::GenerateSecret(
conf->ServerPassword.toStdString(), _authenticationSalt);
// Set log levels if debug is enabled // Set log levels if debug is enabled
if (IsDebugEnabled()) { if (IsDebugEnabled()) {
_server.get_alog().set_channels(websocketpp::log::alevel::all); _server.get_alog().set_channels(websocketpp::log::alevel::all);
_server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control); _server.get_alog().clear_channels(
websocketpp::log::alevel::frame_header |
websocketpp::log::alevel::frame_payload |
websocketpp::log::alevel::control);
_server.get_elog().set_channels(websocketpp::log::elevel::all); _server.get_elog().set_channels(websocketpp::log::elevel::all);
_server.get_alog().clear_channels(websocketpp::log::elevel::devel | websocketpp::log::elevel::library); _server.get_alog().clear_channels(
websocketpp::log::elevel::devel |
websocketpp::log::elevel::library);
} else { } else {
_server.get_alog().clear_channels(websocketpp::log::alevel::all); _server.get_alog().clear_channels(
_server.get_elog().clear_channels(websocketpp::log::elevel::all); websocketpp::log::alevel::all);
_server.get_elog().clear_channels(
websocketpp::log::elevel::all);
} }
_server.reset(); _server.reset();
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
if (conf->BindLoopback) { if (conf->BindLoopback) {
std::string addr = Utils::Platform::GetLoopbackAddress(!conf->Ipv4Only); std::string addr =
Utils::Platform::GetLoopbackAddress(!conf->Ipv4Only);
if (addr.empty()) { if (addr.empty()) {
blog(LOG_ERROR, "[WebSocketServer::Start] Failed to find loopback interface. Server not started."); blog(LOG_ERROR,
"[WebSocketServer::Start] Failed to find loopback interface. Server not started.");
return; return;
} }
_server.listen(addr, std::to_string(conf->ServerPort), errorCode); _server.listen(addr, std::to_string(conf->ServerPort),
blog(LOG_INFO, "[WebSocketServer::Start] Locked to loopback interface."); errorCode);
blog(LOG_INFO,
"[WebSocketServer::Start] Locked to loopback interface.");
} else if (conf->Ipv4Only) { } else if (conf->Ipv4Only) {
_server.listen(websocketpp::lib::asio::ip::tcp::v4(), conf->ServerPort, errorCode); _server.listen(websocketpp::lib::asio::ip::tcp::v4(),
blog(LOG_INFO, "[WebSocketServer::Start] Locked to IPv4 bindings."); conf->ServerPort, errorCode);
blog(LOG_INFO,
"[WebSocketServer::Start] Locked to IPv4 bindings.");
} else { } else {
_server.listen(conf->ServerPort, errorCode); _server.listen(conf->ServerPort, errorCode);
blog(LOG_INFO, "[WebSocketServer::Start] Bound to all interfaces."); blog(LOG_INFO,
"[WebSocketServer::Start] Bound to all interfaces.");
} }
if (errorCode) { if (errorCode) {
std::string errorCodeMessage = errorCode.message(); std::string errorCodeMessage = errorCode.message();
blog(LOG_ERROR, "[WebSocketServer::Start] Listen failed: %s", errorCodeMessage.c_str()); blog(LOG_ERROR, "[WebSocketServer::Start] Listen failed: %s",
errorCodeMessage.c_str());
return; return;
} }
@ -155,30 +165,37 @@ void WebSocketServer::Start()
_serverThread = std::thread(&WebSocketServer::ServerRunner, this); _serverThread = std::thread(&WebSocketServer::ServerRunner, this);
blog(LOG_INFO, "[WebSocketServer::Start] Server started successfully on port %d. Possible connect address: %s", conf->ServerPort.load(), Utils::Platform::GetLocalAddress().c_str()); blog(LOG_INFO,
"[WebSocketServer::Start] Server started successfully on port %d. Possible connect address: %s",
conf->ServerPort.load(),
Utils::Platform::GetLocalAddress().c_str());
} }
void WebSocketServer::Stop() void WebSocketServer::Stop()
{ {
if (!_server.is_listening()) { if (!_server.is_listening()) {
blog(LOG_WARNING, "[WebSocketServer::Stop] Call to Stop() but the server is not listening."); blog(LOG_WARNING,
"[WebSocketServer::Stop] Call to Stop() but the server is not listening.");
return; return;
} }
_server.stop_listening(); _server.stop_listening();
std::unique_lock<std::mutex> lock(_sessionMutex); std::unique_lock<std::mutex> lock(_sessionMutex);
for (auto const& [hdl, session] : _sessions) { for (auto const &[hdl, session] : _sessions) {
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
_server.pause_reading(hdl, errorCode); _server.pause_reading(hdl, errorCode);
if (errorCode) { if (errorCode) {
blog(LOG_INFO, "[WebSocketServer::Stop] Error: %s", errorCode.message().c_str()); blog(LOG_INFO, "[WebSocketServer::Stop] Error: %s",
errorCode.message().c_str());
continue; continue;
} }
_server.close(hdl, websocketpp::close::status::going_away, "Server stopping.", errorCode); _server.close(hdl, websocketpp::close::status::going_away,
"Server stopping.", errorCode);
if (errorCode) { if (errorCode) {
blog(LOG_INFO, "[WebSocketServer::Stop] Error: %s", errorCode.message().c_str()); blog(LOG_INFO, "[WebSocketServer::Stop] Error: %s",
errorCode.message().c_str());
continue; continue;
} }
} }
@ -198,35 +215,42 @@ void WebSocketServer::Stop()
void WebSocketServer::InvalidateSession(websocketpp::connection_hdl hdl) void WebSocketServer::InvalidateSession(websocketpp::connection_hdl hdl)
{ {
blog(LOG_INFO, "[WebSocketServer::InvalidateSession] Invalidating a session."); blog(LOG_INFO,
"[WebSocketServer::InvalidateSession] Invalidating a session.");
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
_server.pause_reading(hdl, errorCode); _server.pause_reading(hdl, errorCode);
if (errorCode) { if (errorCode) {
blog(LOG_INFO, "[WebSocketServer::InvalidateSession] Error: %s", errorCode.message().c_str()); blog(LOG_INFO, "[WebSocketServer::InvalidateSession] Error: %s",
errorCode.message().c_str());
return; return;
} }
_server.close(hdl, WebSocketCloseCode::SessionInvalidated, "Your session has been invalidated.", errorCode); _server.close(hdl, WebSocketCloseCode::SessionInvalidated,
"Your session has been invalidated.", errorCode);
if (errorCode) { if (errorCode) {
blog(LOG_INFO, "[WebSocketServer::InvalidateSession] Error: %s", errorCode.message().c_str()); blog(LOG_INFO, "[WebSocketServer::InvalidateSession] Error: %s",
errorCode.message().c_str());
return; return;
} }
} }
std::vector<WebSocketServer::WebSocketSessionState> WebSocketServer::GetWebSocketSessions() std::vector<WebSocketServer::WebSocketSessionState>
WebSocketServer::GetWebSocketSessions()
{ {
std::vector<WebSocketServer::WebSocketSessionState> webSocketSessions; std::vector<WebSocketServer::WebSocketSessionState> webSocketSessions;
std::unique_lock<std::mutex> lock(_sessionMutex); std::unique_lock<std::mutex> lock(_sessionMutex);
for (auto & [hdl, session] : _sessions) { for (auto &[hdl, session] : _sessions) {
uint64_t connectedAt = session->ConnectedAt(); uint64_t connectedAt = session->ConnectedAt();
uint64_t incomingMessages = session->IncomingMessages(); uint64_t incomingMessages = session->IncomingMessages();
uint64_t outgoingMessages = session->OutgoingMessages(); uint64_t outgoingMessages = session->OutgoingMessages();
std::string remoteAddress = session->RemoteAddress(); std::string remoteAddress = session->RemoteAddress();
bool isIdentified = session->IsIdentified(); bool isIdentified = session->IsIdentified();
webSocketSessions.emplace_back(WebSocketSessionState{hdl, remoteAddress, connectedAt, incomingMessages, outgoingMessages, isIdentified}); webSocketSessions.emplace_back(WebSocketSessionState{
hdl, remoteAddress, connectedAt, incomingMessages,
outgoingMessages, isIdentified});
} }
lock.unlock(); lock.unlock();
@ -237,12 +261,14 @@ void WebSocketServer::onObsLoaded()
{ {
auto conf = GetConfig(); auto conf = GetConfig();
if (!conf) { if (!conf) {
blog(LOG_ERROR, "[WebSocketServer::onObsLoaded] Unable to retreive config!"); blog(LOG_ERROR,
"[WebSocketServer::onObsLoaded] Unable to retreive config!");
return; return;
} }
if (conf->ServerEnabled) { if (conf->ServerEnabled) {
blog(LOG_INFO, "[WebSocketServer::onObsLoaded] WebSocket server is enabled, starting..."); blog(LOG_INFO,
"[WebSocketServer::onObsLoaded] WebSocket server is enabled, starting...");
Start(); Start();
} }
} }
@ -251,9 +277,11 @@ bool WebSocketServer::onValidate(websocketpp::connection_hdl hdl)
{ {
auto conn = _server.get_con_from_hdl(hdl); auto conn = _server.get_con_from_hdl(hdl);
std::vector<std::string> requestedSubprotocols = conn->get_requested_subprotocols(); std::vector<std::string> requestedSubprotocols =
conn->get_requested_subprotocols();
for (auto subprotocol : requestedSubprotocols) { for (auto subprotocol : requestedSubprotocols) {
if (subprotocol == "obswebsocket.json" || subprotocol == "obswebsocket.msgpack") { if (subprotocol == "obswebsocket.json" ||
subprotocol == "obswebsocket.msgpack") {
conn->select_subprotocol(subprotocol); conn->select_subprotocol(subprotocol);
break; break;
} }
@ -268,13 +296,15 @@ void WebSocketServer::onOpen(websocketpp::connection_hdl hdl)
auto conf = GetConfig(); auto conf = GetConfig();
if (!conf) { if (!conf) {
blog(LOG_ERROR, "[WebSocketServer::onOpen] Unable to retreive config!"); blog(LOG_ERROR,
"[WebSocketServer::onOpen] Unable to retreive config!");
return; return;
} }
// Build new session // Build new session
std::unique_lock<std::mutex> lock(_sessionMutex); std::unique_lock<std::mutex> lock(_sessionMutex);
SessionPtr session = _sessions[hdl] = std::make_shared<WebSocketSession>(); SessionPtr session = _sessions[hdl] =
std::make_shared<WebSocketSession>();
std::unique_lock<std::mutex> sessionLock(session->OperationMutex); std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
lock.unlock(); lock.unlock();
@ -299,8 +329,10 @@ void WebSocketServer::onOpen(websocketpp::connection_hdl hdl)
std::string sessionChallenge = Utils::Crypto::GenerateSalt(); std::string sessionChallenge = Utils::Crypto::GenerateSalt();
session->SetChallenge(sessionChallenge); session->SetChallenge(sessionChallenge);
helloMessageData["authentication"] = json::object(); helloMessageData["authentication"] = json::object();
helloMessageData["authentication"]["challenge"] = sessionChallenge; helloMessageData["authentication"]["challenge"] =
helloMessageData["authentication"]["salt"] = _authenticationSalt; sessionChallenge;
helloMessageData["authentication"]["salt"] =
_authenticationSalt;
} }
json helloMessage; json helloMessage;
helloMessage["op"] = 0; helloMessage["op"] = 0;
@ -320,20 +352,27 @@ void WebSocketServer::onOpen(websocketpp::connection_hdl hdl)
emit ClientConnected(state); emit ClientConnected(state);
// Log connection // Log connection
blog(LOG_INFO, "[WebSocketServer::onOpen] New WebSocket client has connected from %s", session->RemoteAddress().c_str()); blog(LOG_INFO,
"[WebSocketServer::onOpen] New WebSocket client has connected from %s",
session->RemoteAddress().c_str());
blog_debug("[WebSocketServer::onOpen] Sending Op 0 (Hello) message:\n%s", helloMessage.dump(2).c_str()); blog_debug(
"[WebSocketServer::onOpen] Sending Op 0 (Hello) message:\n%s",
helloMessage.dump(2).c_str());
// Send object to client // Send object to client
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
auto sessionEncoding = session->Encoding(); auto sessionEncoding = session->Encoding();
if (sessionEncoding == WebSocketEncoding::Json) { if (sessionEncoding == WebSocketEncoding::Json) {
std::string helloMessageJson = helloMessage.dump(); std::string helloMessageJson = helloMessage.dump();
_server.send(hdl, helloMessageJson, websocketpp::frame::opcode::text, errorCode); _server.send(hdl, helloMessageJson,
websocketpp::frame::opcode::text, errorCode);
} else if (sessionEncoding == WebSocketEncoding::MsgPack) { } else if (sessionEncoding == WebSocketEncoding::MsgPack) {
auto msgPackData = json::to_msgpack(helloMessage); auto msgPackData = json::to_msgpack(helloMessage);
std::string messageMsgPack(msgPackData.begin(), msgPackData.end()); std::string messageMsgPack(msgPackData.begin(),
_server.send(hdl, messageMsgPack, websocketpp::frame::opcode::binary, errorCode); msgPackData.end());
_server.send(hdl, messageMsgPack,
websocketpp::frame::opcode::binary, errorCode);
} }
session->IncrementOutgoingMessages(); session->IncrementOutgoingMessages();
} }
@ -372,24 +411,37 @@ void WebSocketServer::onClose(websocketpp::connection_hdl hdl)
emit ClientDisconnected(state, conn->get_local_close_code()); emit ClientDisconnected(state, conn->get_local_close_code());
// Log disconnection // Log disconnection
blog(LOG_INFO, "[WebSocketServer::onClose] WebSocket client %s has disconnected", remoteAddress.c_str()); blog(LOG_INFO,
"[WebSocketServer::onClose] WebSocket client %s has disconnected",
remoteAddress.c_str());
// Get config for tray notification // Get config for tray notification
auto conf = GetConfig(); auto conf = GetConfig();
if (!conf) { if (!conf) {
blog(LOG_ERROR, "[WebSocketServer::onClose] Unable to retreive config!"); blog(LOG_ERROR,
"[WebSocketServer::onClose] Unable to retreive config!");
return; return;
} }
// If previously identified, not going away, and notifications enabled, send a tray notification // If previously identified, not going away, and notifications enabled, send a tray notification
if (isIdentified && (conn->get_local_close_code() != websocketpp::close::status::going_away) && conf->AlertsEnabled) { if (isIdentified &&
QString title = obs_module_text("OBSWebSocket.TrayNotification.Disconnected.Title"); (conn->get_local_close_code() !=
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Disconnected.Body")).arg(QString::fromStdString(remoteAddress)); websocketpp::close::status::going_away) &&
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Information, title, body); conf->AlertsEnabled) {
QString title = obs_module_text(
"OBSWebSocket.TrayNotification.Disconnected.Title");
QString body =
QString(obs_module_text(
"OBSWebSocket.TrayNotification.Disconnected.Body"))
.arg(QString::fromStdString(remoteAddress));
Utils::Platform::SendTrayNotification(
QSystemTrayIcon::Information, title, body);
} }
} }
void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message) void WebSocketServer::onMessage(
websocketpp::connection_hdl hdl,
websocketpp::server<websocketpp::config::asio>::message_ptr message)
{ {
auto opCode = message->get_opcode(); auto opCode = message->get_opcode();
std::string payload = message->get_payload(); std::string payload = message->get_payload();
@ -398,7 +450,7 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
SessionPtr session; SessionPtr session;
try { try {
session = _sessions.at(hdl); session = _sessions.at(hdl);
} catch (const std::out_of_range& oor) { } catch (const std::out_of_range &oor) {
return; return;
} }
lock.unlock(); lock.unlock();
@ -412,31 +464,52 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
uint8_t sessionEncoding = session->Encoding(); uint8_t sessionEncoding = session->Encoding();
if (sessionEncoding == WebSocketEncoding::Json) { if (sessionEncoding == WebSocketEncoding::Json) {
if (opCode != websocketpp::frame::opcode::text) { if (opCode != websocketpp::frame::opcode::text) {
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, "Your session encoding is set to Json, but a binary message was received.", errorCode); _server.close(
hdl,
WebSocketCloseCode::MessageDecodeError,
"Your session encoding is set to Json, but a binary message was received.",
errorCode);
return; return;
} }
try { try {
incomingMessage = json::parse(payload); incomingMessage = json::parse(payload);
} catch (json::parse_error& e) { } catch (json::parse_error &e) {
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, std::string("Unable to decode Json: ") + e.what(), errorCode); _server.close(
hdl,
WebSocketCloseCode::MessageDecodeError,
std::string("Unable to decode Json: ") +
e.what(),
errorCode);
return; return;
} }
} else if (sessionEncoding == WebSocketEncoding::MsgPack) { } else if (sessionEncoding == WebSocketEncoding::MsgPack) {
if (opCode != websocketpp::frame::opcode::binary) { if (opCode != websocketpp::frame::opcode::binary) {
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, "Your session encoding is set to MsgPack, but a text message was received.", errorCode); _server.close(
hdl,
WebSocketCloseCode::MessageDecodeError,
"Your session encoding is set to MsgPack, but a text message was received.",
errorCode);
return; return;
} }
try { try {
incomingMessage = json::from_msgpack(payload); incomingMessage = json::from_msgpack(payload);
} catch (json::parse_error& e) { } catch (json::parse_error &e) {
_server.close(hdl, WebSocketCloseCode::MessageDecodeError, std::string("Unable to decode MsgPack: ") + e.what(), errorCode); _server.close(
hdl,
WebSocketCloseCode::MessageDecodeError,
std::string(
"Unable to decode MsgPack: ") +
e.what(),
errorCode);
return; return;
} }
} }
blog_debug("[WebSocketServer::onMessage] Incoming message (decoded):\n%s", incomingMessage.dump(2).c_str()); blog_debug(
"[WebSocketServer::onMessage] Incoming message (decoded):\n%s",
incomingMessage.dump(2).c_str());
ProcessResult ret; ProcessResult ret;
@ -448,10 +521,15 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
} }
// Disconnect client if 4.x protocol is detected // Disconnect client if 4.x protocol is detected
if (!session->IsIdentified() && incomingMessage.contains("request-type")) { if (!session->IsIdentified() &&
blog(LOG_WARNING, "[WebSocketServer::onMessage] Client %s appears to be running a pre-5.0.0 protocol.", session->RemoteAddress().c_str()); incomingMessage.contains("request-type")) {
ret.closeCode = WebSocketCloseCode::UnsupportedRpcVersion; blog(LOG_WARNING,
ret.closeReason = "You appear to be attempting to connect with the pre-5.0.0 plugin protocol. Check to make sure your client is updated."; "[WebSocketServer::onMessage] Client %s appears to be running a pre-5.0.0 protocol.",
session->RemoteAddress().c_str());
ret.closeCode =
WebSocketCloseCode::UnsupportedRpcVersion;
ret.closeReason =
"You appear to be attempting to connect with the pre-5.0.0 plugin protocol. Check to make sure your client is updated.";
goto skipProcessing; goto skipProcessing;
} }
@ -462,31 +540,44 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
goto skipProcessing; goto skipProcessing;
} }
ProcessMessage(session, ret, incomingMessage["op"], incomingMessage["d"]); ProcessMessage(session, ret, incomingMessage["op"],
incomingMessage["d"]);
skipProcessing: skipProcessing:
if (ret.closeCode != WebSocketCloseCode::DontClose) { if (ret.closeCode != WebSocketCloseCode::DontClose) {
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
_server.close(hdl, ret.closeCode, ret.closeReason, errorCode); _server.close(hdl, ret.closeCode, ret.closeReason,
errorCode);
return; return;
} }
if (!ret.result.is_null()) { if (!ret.result.is_null()) {
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
if (sessionEncoding == WebSocketEncoding::Json) { if (sessionEncoding == WebSocketEncoding::Json) {
std::string helloMessageJson = ret.result.dump(); std::string helloMessageJson =
_server.send(hdl, helloMessageJson, websocketpp::frame::opcode::text, errorCode); ret.result.dump();
} else if (sessionEncoding == WebSocketEncoding::MsgPack) { _server.send(hdl, helloMessageJson,
websocketpp::frame::opcode::text,
errorCode);
} else if (sessionEncoding ==
WebSocketEncoding::MsgPack) {
auto msgPackData = json::to_msgpack(ret.result); auto msgPackData = json::to_msgpack(ret.result);
std::string messageMsgPack(msgPackData.begin(), msgPackData.end()); std::string messageMsgPack(msgPackData.begin(),
_server.send(hdl, messageMsgPack, websocketpp::frame::opcode::binary, errorCode); msgPackData.end());
_server.send(hdl, messageMsgPack,
websocketpp::frame::opcode::binary,
errorCode);
} }
session->IncrementOutgoingMessages(); session->IncrementOutgoingMessages();
blog_debug("[WebSocketServer::onMessage] Outgoing message:\n%s", ret.result.dump(2).c_str()); blog_debug(
"[WebSocketServer::onMessage] Outgoing message:\n%s",
ret.result.dump(2).c_str());
if (errorCode) if (errorCode)
blog(LOG_WARNING, "[WebSocketServer::onMessage] Sending message to client failed: %s", errorCode.message().c_str()); blog(LOG_WARNING,
"[WebSocketServer::onMessage] Sending message to client failed: %s",
errorCode.message().c_str());
} }
})); }));
} }

View File

@ -34,73 +34,79 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "../requesthandler/rpc/Request.h" #include "../requesthandler/rpc/Request.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
class WebSocketServer : QObject class WebSocketServer : QObject {
{
Q_OBJECT Q_OBJECT
public: public:
enum WebSocketEncoding { enum WebSocketEncoding { Json, MsgPack };
Json,
MsgPack
};
struct WebSocketSessionState { struct WebSocketSessionState {
websocketpp::connection_hdl hdl; websocketpp::connection_hdl hdl;
std::string remoteAddress; std::string remoteAddress;
uint64_t connectedAt; uint64_t connectedAt;
uint64_t incomingMessages; uint64_t incomingMessages;
uint64_t outgoingMessages; uint64_t outgoingMessages;
bool isIdentified; bool isIdentified;
}; };
WebSocketServer(); WebSocketServer();
~WebSocketServer(); ~WebSocketServer();
void Start(); void Start();
void Stop(); void Stop();
void InvalidateSession(websocketpp::connection_hdl hdl); void InvalidateSession(websocketpp::connection_hdl hdl);
void BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData = nullptr, uint8_t rpcVersion = 0); void BroadcastEvent(uint64_t requiredIntent,
const std::string &eventType,
const json &eventData = nullptr,
uint8_t rpcVersion = 0);
bool IsListening() { bool IsListening() { return _server.is_listening(); }
return _server.is_listening();
}
std::vector<WebSocketSessionState> GetWebSocketSessions(); std::vector<WebSocketSessionState> GetWebSocketSessions();
QThreadPool *GetThreadPool() { QThreadPool *GetThreadPool() { return &_threadPool; }
return &_threadPool;
}
signals: signals:
void ClientConnected(WebSocketSessionState state); void ClientConnected(WebSocketSessionState state);
void ClientDisconnected(WebSocketSessionState state, uint16_t closeCode); void ClientDisconnected(WebSocketSessionState state,
uint16_t closeCode);
private: private:
struct ProcessResult { struct ProcessResult {
WebSocketCloseCode::WebSocketCloseCode closeCode = WebSocketCloseCode::DontClose; WebSocketCloseCode::WebSocketCloseCode closeCode =
std::string closeReason; WebSocketCloseCode::DontClose;
json result; std::string closeReason;
}; json result;
};
void ServerRunner(); void ServerRunner();
void onObsLoaded(); void onObsLoaded();
bool onValidate(websocketpp::connection_hdl hdl); bool onValidate(websocketpp::connection_hdl hdl);
void onOpen(websocketpp::connection_hdl hdl); void onOpen(websocketpp::connection_hdl hdl);
void onClose(websocketpp::connection_hdl hdl); void onClose(websocketpp::connection_hdl hdl);
void onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message); void
onMessage(websocketpp::connection_hdl hdl,
websocketpp::server<websocketpp::config::asio>::message_ptr
message);
static void SetSessionParameters(SessionPtr session, WebSocketServer::ProcessResult &ret, const json &payloadData); static void SetSessionParameters(SessionPtr session,
void ProcessMessage(SessionPtr session, ProcessResult &ret, WebSocketOpCode::WebSocketOpCode opCode, json &payloadData); WebSocketServer::ProcessResult &ret,
const json &payloadData);
void ProcessMessage(SessionPtr session, ProcessResult &ret,
WebSocketOpCode::WebSocketOpCode opCode,
json &payloadData);
QThreadPool _threadPool; QThreadPool _threadPool;
std::thread _serverThread; std::thread _serverThread;
websocketpp::server<websocketpp::config::asio> _server; websocketpp::server<websocketpp::config::asio> _server;
std::string _authenticationSecret; std::string _authenticationSecret;
std::string _authenticationSalt; std::string _authenticationSalt;
std::mutex _sessionMutex; std::mutex _sessionMutex;
std::map<websocketpp::connection_hdl, SessionPtr, std::owner_less<websocketpp::connection_hdl>> _sessions; std::map<websocketpp::connection_hdl, SessionPtr,
std::owner_less<websocketpp::connection_hdl>>
_sessions;
}; };

View File

@ -35,19 +35,20 @@ static bool IsSupportedRpcVersion(uint8_t requestedVersion)
return (requestedVersion == 1); return (requestedVersion == 1);
} }
static json ConstructRequestResult(RequestResult requestResult, const json &requestJson) static json ConstructRequestResult(RequestResult requestResult,
const json &requestJson)
{ {
json ret; json ret;
ret["requestType"] = requestJson["requestType"]; ret["requestType"] = requestJson["requestType"];
if (requestJson.contains("requestId") && !requestJson["requestId"].is_null()) if (requestJson.contains("requestId") &&
!requestJson["requestId"].is_null())
ret["requestId"] = requestJson["requestId"]; ret["requestId"] = requestJson["requestId"];
ret["requestStatus"] = { ret["requestStatus"] = {{"result", requestResult.StatusCode ==
{"result", requestResult.StatusCode == RequestStatus::Success}, RequestStatus::Success},
{"code", requestResult.StatusCode} {"code", requestResult.StatusCode}};
};
if (!requestResult.Comment.empty()) if (!requestResult.Comment.empty())
ret["requestStatus"]["comment"] = requestResult.Comment; ret["requestStatus"]["comment"] = requestResult.Comment;
@ -58,27 +59,37 @@ static json ConstructRequestResult(RequestResult requestResult, const json &requ
return ret; return ret;
} }
void WebSocketServer::SetSessionParameters(SessionPtr session, ProcessResult &ret, const json &payloadData) void WebSocketServer::SetSessionParameters(SessionPtr session,
ProcessResult &ret,
const json &payloadData)
{ {
if (payloadData.contains("eventSubscriptions")) { if (payloadData.contains("eventSubscriptions")) {
if (!payloadData["eventSubscriptions"].is_number_unsigned()) { if (!payloadData["eventSubscriptions"].is_number_unsigned()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType; ret.closeCode =
ret.closeReason = "Your `eventSubscriptions` is not an unsigned number."; WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `eventSubscriptions` is not an unsigned number.";
return; return;
} }
session->SetEventSubscriptions(payloadData["eventSubscriptions"]); session->SetEventSubscriptions(
payloadData["eventSubscriptions"]);
} }
} }
void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::ProcessResult &ret, WebSocketOpCode::WebSocketOpCode opCode, json &payloadData) void WebSocketServer::ProcessMessage(SessionPtr session,
WebSocketServer::ProcessResult &ret,
WebSocketOpCode::WebSocketOpCode opCode,
json &payloadData)
{ {
if (!payloadData.is_object()) { if (!payloadData.is_object()) {
if (payloadData.is_null()) { if (payloadData.is_null()) {
ret.closeCode = WebSocketCloseCode::MissingDataField; ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload is missing data (`d`)."; ret.closeReason = "Your payload is missing data (`d`).";
} else { } else {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType; ret.closeCode =
ret.closeReason = "Your payload's data (`d`) is not an object."; WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your payload's data (`d`) is not an object.";
} }
return; return;
} }
@ -86,223 +97,301 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
// Only `Identify` is allowed when not identified // Only `Identify` is allowed when not identified
if (!session->IsIdentified() && opCode != 1) { if (!session->IsIdentified() && opCode != 1) {
ret.closeCode = WebSocketCloseCode::NotIdentified; ret.closeCode = WebSocketCloseCode::NotIdentified;
ret.closeReason = "You attempted to send a non-Identify message while not identified."; ret.closeReason =
"You attempted to send a non-Identify message while not identified.";
return; return;
} }
switch (opCode) { switch (opCode) {
case WebSocketOpCode::Identify: { // Identify case WebSocketOpCode::Identify: { // Identify
std::unique_lock<std::mutex> sessionLock(session->OperationMutex); std::unique_lock<std::mutex> sessionLock(
if (session->IsIdentified()) { session->OperationMutex);
ret.closeCode = WebSocketCloseCode::AlreadyIdentified; if (session->IsIdentified()) {
ret.closeReason = "You are already Identified with the obs-websocket server."; ret.closeCode = WebSocketCloseCode::AlreadyIdentified;
return; ret.closeReason =
} "You are already Identified with the obs-websocket server.";
if (session->AuthenticationRequired()) {
if (!payloadData.contains("authentication")) {
ret.closeCode = WebSocketCloseCode::AuthenticationFailed;
ret.closeReason = "Your payload's data is missing an `authentication` string, however authentication is required.";
return;
}
if (!Utils::Crypto::CheckAuthenticationString(session->Secret(), session->Challenge(), payloadData["authentication"])) {
auto conf = GetConfig();
if (conf && conf->AlertsEnabled) {
QString title = obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Title");
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Body")).arg(QString::fromStdString(session->RemoteAddress()));
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Warning, title, body);
}
ret.closeCode = WebSocketCloseCode::AuthenticationFailed;
ret.closeReason = "Authentication failed.";
return;
}
}
if (!payloadData.contains("rpcVersion")) {
ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload's data is missing an `rpcVersion`.";
return;
}
if (!payloadData["rpcVersion"].is_number_unsigned()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason = "Your `rpcVersion` is not an unsigned number.";
}
uint8_t requestedRpcVersion = payloadData["rpcVersion"];
if (!IsSupportedRpcVersion(requestedRpcVersion)) {
ret.closeCode = WebSocketCloseCode::UnsupportedRpcVersion;
ret.closeReason = "Your requested RPC version is not supported by this server.";
return;
}
session->SetRpcVersion(requestedRpcVersion);
SetSessionParameters(session, ret, payloadData);
if (ret.closeCode != WebSocketCloseCode::DontClose) {
return;
}
// Increment refs for event subscriptions
auto eventHandler = GetEventHandler();
eventHandler->ProcessSubscription(session->EventSubscriptions());
// Mark session as identified
session->SetIsIdentified(true);
// Send desktop notification. TODO: Move to UI code
auto conf = GetConfig();
if (conf && conf->AlertsEnabled) {
QString title = obs_module_text("OBSWebSocket.TrayNotification.Identified.Title");
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Identified.Body")).arg(QString::fromStdString(session->RemoteAddress()));
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Information, title, body);
}
ret.result["op"] = WebSocketOpCode::Identified;
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
} return;
case WebSocketOpCode::Reidentify: { // Reidentify
std::unique_lock<std::mutex> sessionLock(session->OperationMutex);
// Decrement refs for current subscriptions
auto eventHandler = GetEventHandler();
eventHandler->ProcessUnsubscription(session->EventSubscriptions());
SetSessionParameters(session, ret, payloadData);
if (ret.closeCode != WebSocketCloseCode::DontClose) {
return;
}
// Increment refs for new subscriptions
eventHandler->ProcessSubscription(session->EventSubscriptions());
ret.result["op"] = WebSocketOpCode::Identified;
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
} return;
case WebSocketOpCode::Request: { // Request
// RequestID checking has to be done here where we are able to close the connection.
if (!payloadData.contains("requestId")) {
ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload data is missing a `requestId`.";
return;
}
RequestHandler requestHandler(session);
Request request(payloadData["requestType"], payloadData["requestData"]);
RequestResult requestResult = requestHandler.ProcessRequest(request);
json resultPayloadData;
resultPayloadData["requestType"] = payloadData["requestType"];
resultPayloadData["requestId"] = payloadData["requestId"];
resultPayloadData["requestStatus"] = {
{"result", requestResult.StatusCode == RequestStatus::Success},
{"code", requestResult.StatusCode}
};
if (!requestResult.Comment.empty())
resultPayloadData["requestStatus"]["comment"] = requestResult.Comment;
if (requestResult.ResponseData.is_object())
resultPayloadData["responseData"] = requestResult.ResponseData;
ret.result["op"] = WebSocketOpCode::RequestResponse;
ret.result["d"] = resultPayloadData;
} return;
case WebSocketOpCode::RequestBatch: { // RequestBatch
// RequestID checking has to be done here where we are able to close the connection.
if (!payloadData.contains("requestId")) {
ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload data is missing a `requestId`.";
return;
}
if (!payloadData.contains("requests")) {
ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload data is missing a `requests`.";
return;
}
if (!payloadData["requests"].is_array()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason = "Your `requests` is not an array.";
return;
}
RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::SerialRealtime;
if (payloadData.contains("executionType") && !payloadData["executionType"].is_null()) {
if (!payloadData["executionType"].is_number_unsigned()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason = "Your `executionType` is not a number.";
return;
}
int8_t requestedExecutionType = payloadData["executionType"];
if (!RequestBatchExecutionType::IsValid(requestedExecutionType) || requestedExecutionType == RequestBatchExecutionType::None) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldValue;
ret.closeReason = "Your `executionType` has an invalid value.";
return;
}
// The thread pool must support 2 or more threads else parallel requests will deadlock.
if (requestedExecutionType == RequestBatchExecutionType::Parallel && _threadPool.maxThreadCount() < 2) {
ret.closeCode = WebSocketCloseCode::UnsupportedFeature;
ret.closeReason = "Parallel request batch processing is not available on this system due to limited core count.";
return;
}
executionType = (RequestBatchExecutionType::RequestBatchExecutionType)requestedExecutionType;
}
if (payloadData.contains("variables") && !payloadData["variables"].is_null()) {
if (!payloadData.is_object()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason = "Your `variables` is not an object.";
return;
}
if (executionType == RequestBatchExecutionType::Parallel) {
ret.closeCode = WebSocketCloseCode::UnsupportedFeature;
ret.closeReason = "Variables are not supported in Parallel mode.";
return;
}
}
bool haltOnFailure = false;
if (payloadData.contains("haltOnFailure") && !payloadData["haltOnFailure"].is_null()) {
if (!payloadData["haltOnFailure"].is_boolean()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason = "Your `haltOnFailure` is not a boolean.";
return;
}
haltOnFailure = payloadData["haltOnFailure"];
}
std::vector<json> requests = payloadData["requests"];
std::vector<RequestBatchRequest> requestsVector;
for (auto &requestJson : requests)
requestsVector.emplace_back(requestJson["requestType"], requestJson["requestData"], executionType, requestJson["inputVariables"], requestJson["outputVariables"]);
auto resultsVector = RequestBatchHandler::ProcessRequestBatch(_threadPool, session, executionType, requestsVector, payloadData["variables"], haltOnFailure);
size_t i = 0;
std::vector<json> results;
for (auto &requestResult : resultsVector) {
results.push_back(ConstructRequestResult(requestResult, requests[i]));
i++;
}
ret.result["op"] = WebSocketOpCode::RequestBatchResponse;
ret.result["d"]["requestId"] = payloadData["requestId"];
ret.result["d"]["results"] = results;
} return;
default:
ret.closeCode = WebSocketCloseCode::UnknownOpCode;
ret.closeReason = std::string("Unknown OpCode: ") + std::to_string(opCode);
return; return;
}
if (session->AuthenticationRequired()) {
if (!payloadData.contains("authentication")) {
ret.closeCode =
WebSocketCloseCode::AuthenticationFailed;
ret.closeReason =
"Your payload's data is missing an `authentication` string, however authentication is required.";
return;
}
if (!Utils::Crypto::CheckAuthenticationString(
session->Secret(), session->Challenge(),
payloadData["authentication"])) {
auto conf = GetConfig();
if (conf && conf->AlertsEnabled) {
QString title = obs_module_text(
"OBSWebSocket.TrayNotification.AuthenticationFailed.Title");
QString body =
QString(obs_module_text(
"OBSWebSocket.TrayNotification.AuthenticationFailed.Body"))
.arg(QString::fromStdString(
session->RemoteAddress()));
Utils::Platform::SendTrayNotification(
QSystemTrayIcon::Warning, title,
body);
}
ret.closeCode =
WebSocketCloseCode::AuthenticationFailed;
ret.closeReason = "Authentication failed.";
return;
}
}
if (!payloadData.contains("rpcVersion")) {
ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason =
"Your payload's data is missing an `rpcVersion`.";
return;
}
if (!payloadData["rpcVersion"].is_number_unsigned()) {
ret.closeCode =
WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `rpcVersion` is not an unsigned number.";
}
uint8_t requestedRpcVersion = payloadData["rpcVersion"];
if (!IsSupportedRpcVersion(requestedRpcVersion)) {
ret.closeCode =
WebSocketCloseCode::UnsupportedRpcVersion;
ret.closeReason =
"Your requested RPC version is not supported by this server.";
return;
}
session->SetRpcVersion(requestedRpcVersion);
SetSessionParameters(session, ret, payloadData);
if (ret.closeCode != WebSocketCloseCode::DontClose) {
return;
}
// Increment refs for event subscriptions
auto eventHandler = GetEventHandler();
eventHandler->ProcessSubscription(
session->EventSubscriptions());
// Mark session as identified
session->SetIsIdentified(true);
// Send desktop notification. TODO: Move to UI code
auto conf = GetConfig();
if (conf && conf->AlertsEnabled) {
QString title = obs_module_text(
"OBSWebSocket.TrayNotification.Identified.Title");
QString body =
QString(obs_module_text(
"OBSWebSocket.TrayNotification.Identified.Body"))
.arg(QString::fromStdString(
session->RemoteAddress()));
Utils::Platform::SendTrayNotification(
QSystemTrayIcon::Information, title, body);
}
ret.result["op"] = WebSocketOpCode::Identified;
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
}
return;
case WebSocketOpCode::Reidentify: { // Reidentify
std::unique_lock<std::mutex> sessionLock(
session->OperationMutex);
// Decrement refs for current subscriptions
auto eventHandler = GetEventHandler();
eventHandler->ProcessUnsubscription(
session->EventSubscriptions());
SetSessionParameters(session, ret, payloadData);
if (ret.closeCode != WebSocketCloseCode::DontClose) {
return;
}
// Increment refs for new subscriptions
eventHandler->ProcessSubscription(
session->EventSubscriptions());
ret.result["op"] = WebSocketOpCode::Identified;
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
}
return;
case WebSocketOpCode::Request: { // Request
// RequestID checking has to be done here where we are able to close the connection.
if (!payloadData.contains("requestId")) {
ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason =
"Your payload data is missing a `requestId`.";
return;
}
RequestHandler requestHandler(session);
Request request(payloadData["requestType"],
payloadData["requestData"]);
RequestResult requestResult =
requestHandler.ProcessRequest(request);
json resultPayloadData;
resultPayloadData["requestType"] = payloadData["requestType"];
resultPayloadData["requestId"] = payloadData["requestId"];
resultPayloadData["requestStatus"] = {
{"result",
requestResult.StatusCode == RequestStatus::Success},
{"code", requestResult.StatusCode}};
if (!requestResult.Comment.empty())
resultPayloadData["requestStatus"]["comment"] =
requestResult.Comment;
if (requestResult.ResponseData.is_object())
resultPayloadData["responseData"] =
requestResult.ResponseData;
ret.result["op"] = WebSocketOpCode::RequestResponse;
ret.result["d"] = resultPayloadData;
}
return;
case WebSocketOpCode::RequestBatch: { // RequestBatch
// RequestID checking has to be done here where we are able to close the connection.
if (!payloadData.contains("requestId")) {
ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason =
"Your payload data is missing a `requestId`.";
return;
}
if (!payloadData.contains("requests")) {
ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason =
"Your payload data is missing a `requests`.";
return;
}
if (!payloadData["requests"].is_array()) {
ret.closeCode =
WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason = "Your `requests` is not an array.";
return;
}
RequestBatchExecutionType::RequestBatchExecutionType
executionType =
RequestBatchExecutionType::SerialRealtime;
if (payloadData.contains("executionType") &&
!payloadData["executionType"].is_null()) {
if (!payloadData["executionType"].is_number_unsigned()) {
ret.closeCode =
WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `executionType` is not a number.";
return;
}
int8_t requestedExecutionType =
payloadData["executionType"];
if (!RequestBatchExecutionType::IsValid(
requestedExecutionType) ||
requestedExecutionType ==
RequestBatchExecutionType::None) {
ret.closeCode =
WebSocketCloseCode::InvalidDataFieldValue;
ret.closeReason =
"Your `executionType` has an invalid value.";
return;
}
// The thread pool must support 2 or more threads else parallel requests will deadlock.
if (requestedExecutionType ==
RequestBatchExecutionType::Parallel &&
_threadPool.maxThreadCount() < 2) {
ret.closeCode =
WebSocketCloseCode::UnsupportedFeature;
ret.closeReason =
"Parallel request batch processing is not available on this system due to limited core count.";
return;
}
executionType = (RequestBatchExecutionType::
RequestBatchExecutionType)
requestedExecutionType;
}
if (payloadData.contains("variables") &&
!payloadData["variables"].is_null()) {
if (!payloadData.is_object()) {
ret.closeCode =
WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `variables` is not an object.";
return;
}
if (executionType ==
RequestBatchExecutionType::Parallel) {
ret.closeCode =
WebSocketCloseCode::UnsupportedFeature;
ret.closeReason =
"Variables are not supported in Parallel mode.";
return;
}
}
bool haltOnFailure = false;
if (payloadData.contains("haltOnFailure") &&
!payloadData["haltOnFailure"].is_null()) {
if (!payloadData["haltOnFailure"].is_boolean()) {
ret.closeCode =
WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `haltOnFailure` is not a boolean.";
return;
}
haltOnFailure = payloadData["haltOnFailure"];
}
std::vector<json> requests = payloadData["requests"];
std::vector<RequestBatchRequest> requestsVector;
for (auto &requestJson : requests)
requestsVector.emplace_back(
requestJson["requestType"],
requestJson["requestData"], executionType,
requestJson["inputVariables"],
requestJson["outputVariables"]);
auto resultsVector = RequestBatchHandler::ProcessRequestBatch(
_threadPool, session, executionType, requestsVector,
payloadData["variables"], haltOnFailure);
size_t i = 0;
std::vector<json> results;
for (auto &requestResult : resultsVector) {
results.push_back(ConstructRequestResult(requestResult,
requests[i]));
i++;
}
ret.result["op"] = WebSocketOpCode::RequestBatchResponse;
ret.result["d"]["requestId"] = payloadData["requestId"];
ret.result["d"]["results"] = results;
}
return;
default:
ret.closeCode = WebSocketCloseCode::UnknownOpCode;
ret.closeReason = std::string("Unknown OpCode: ") +
std::to_string(opCode);
return;
} }
} }
// It isn't consistent to directly call the WebSocketServer from the events system, but it would also be dumb to make it unnecessarily complicated. // It isn't consistent to directly call the WebSocketServer from the events system, but it would also be dumb to make it unnecessarily complicated.
void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string &eventType, const json &eventData, uint8_t rpcVersion) void WebSocketServer::BroadcastEvent(uint64_t requiredIntent,
const std::string &eventType,
const json &eventData, uint8_t rpcVersion)
{ {
if (!_server.is_listening()) if (!_server.is_listening())
return; return;
@ -322,38 +411,61 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string
// Recurse connected sessions and send the event to suitable sessions. // Recurse connected sessions and send the event to suitable sessions.
std::unique_lock<std::mutex> lock(_sessionMutex); std::unique_lock<std::mutex> lock(_sessionMutex);
for (auto & it : _sessions) { for (auto &it : _sessions) {
if (!it.second->IsIdentified()) { if (!it.second->IsIdentified()) {
continue; continue;
} }
if (rpcVersion && it.second->RpcVersion() != rpcVersion) { if (rpcVersion &&
it.second->RpcVersion() != rpcVersion) {
continue; continue;
} }
if ((it.second->EventSubscriptions() & requiredIntent) != 0) { if ((it.second->EventSubscriptions() &
requiredIntent) != 0) {
websocketpp::lib::error_code errorCode; websocketpp::lib::error_code errorCode;
switch (it.second->Encoding()) { switch (it.second->Encoding()) {
case WebSocketEncoding::Json: case WebSocketEncoding::Json:
if (messageJson.empty()) { if (messageJson.empty()) {
messageJson = eventMessage.dump(); messageJson =
} eventMessage.dump();
_server.send((websocketpp::connection_hdl)it.first, messageJson, websocketpp::frame::opcode::text, errorCode); }
it.second->IncrementOutgoingMessages(); _server.send(
break; (websocketpp::connection_hdl)
case WebSocketEncoding::MsgPack: it.first,
if (messageMsgPack.empty()) { messageJson,
auto msgPackData = json::to_msgpack(eventMessage); websocketpp::frame::opcode::text,
messageMsgPack = std::string(msgPackData.begin(), msgPackData.end()); errorCode);
} it.second->IncrementOutgoingMessages();
_server.send((websocketpp::connection_hdl)it.first, messageMsgPack, websocketpp::frame::opcode::binary, errorCode); break;
it.second->IncrementOutgoingMessages(); case WebSocketEncoding::MsgPack:
break; if (messageMsgPack.empty()) {
auto msgPackData =
json::to_msgpack(
eventMessage);
messageMsgPack = std::string(
msgPackData.begin(),
msgPackData.end());
}
_server.send(
(websocketpp::connection_hdl)
it.first,
messageMsgPack,
websocketpp::frame::opcode::binary,
errorCode);
it.second->IncrementOutgoingMessages();
break;
} }
if (errorCode) if (errorCode)
blog(LOG_ERROR, "[WebSocketServer::BroadcastEvent] Error sending event message: %s", errorCode.message().c_str()); blog(LOG_ERROR,
"[WebSocketServer::BroadcastEvent] Error sending event message: %s",
errorCode.message().c_str());
} }
} }
lock.unlock(); lock.unlock();
if (IsDebugEnabled() && (EventSubscription::All & requiredIntent) != 0) // Don't log high volume events if (IsDebugEnabled() &&
blog(LOG_INFO, "[WebSocketServer::BroadcastEvent] Outgoing event:\n%s", eventMessage.dump(2).c_str()); (EventSubscription::All & requiredIntent) !=
0) // Don't log high volume events
blog(LOG_INFO,
"[WebSocketServer::BroadcastEvent] Outgoing event:\n%s",
eventMessage.dump(2).c_str());
})); }));
} }

View File

@ -20,16 +20,16 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "WebSocketSession.h" #include "WebSocketSession.h"
#include "../../eventhandler/types/EventSubscription.h" #include "../../eventhandler/types/EventSubscription.h"
WebSocketSession::WebSocketSession() : WebSocketSession::WebSocketSession()
_remoteAddress(""), : _remoteAddress(""),
_connectedAt(0), _connectedAt(0),
_incomingMessages(0), _incomingMessages(0),
_outgoingMessages(0), _outgoingMessages(0),
_encoding(0), _encoding(0),
_challenge(""), _challenge(""),
_rpcVersion(OBS_WEBSOCKET_RPC_VERSION), _rpcVersion(OBS_WEBSOCKET_RPC_VERSION),
_isIdentified(false), _isIdentified(false),
_eventSubscriptions(EventSubscription::All) _eventSubscriptions(EventSubscription::All)
{ {
} }

View File

@ -29,59 +29,58 @@ with this program. If not, see <https://www.gnu.org/licenses/>
class WebSocketSession; class WebSocketSession;
typedef std::shared_ptr<WebSocketSession> SessionPtr; typedef std::shared_ptr<WebSocketSession> SessionPtr;
class WebSocketSession class WebSocketSession {
{ public:
public: WebSocketSession();
WebSocketSession();
std::string RemoteAddress(); std::string RemoteAddress();
void SetRemoteAddress(std::string address); void SetRemoteAddress(std::string address);
uint64_t ConnectedAt(); uint64_t ConnectedAt();
void SetConnectedAt(uint64_t at); void SetConnectedAt(uint64_t at);
uint64_t IncomingMessages(); uint64_t IncomingMessages();
void IncrementIncomingMessages(); void IncrementIncomingMessages();
uint64_t OutgoingMessages(); uint64_t OutgoingMessages();
void IncrementOutgoingMessages(); void IncrementOutgoingMessages();
uint8_t Encoding(); uint8_t Encoding();
void SetEncoding(uint8_t encoding); void SetEncoding(uint8_t encoding);
bool AuthenticationRequired(); bool AuthenticationRequired();
void SetAuthenticationRequired(bool required); void SetAuthenticationRequired(bool required);
std::string Secret(); std::string Secret();
void SetSecret(std::string secret); void SetSecret(std::string secret);
std::string Challenge(); std::string Challenge();
void SetChallenge(std::string challenge); void SetChallenge(std::string challenge);
uint8_t RpcVersion(); uint8_t RpcVersion();
void SetRpcVersion(uint8_t version); void SetRpcVersion(uint8_t version);
bool IsIdentified(); bool IsIdentified();
void SetIsIdentified(bool identified); void SetIsIdentified(bool identified);
uint64_t EventSubscriptions(); uint64_t EventSubscriptions();
void SetEventSubscriptions(uint64_t subscriptions); void SetEventSubscriptions(uint64_t subscriptions);
std::mutex OperationMutex; std::mutex OperationMutex;
private: private:
std::mutex _remoteAddressMutex; std::mutex _remoteAddressMutex;
std::string _remoteAddress; std::string _remoteAddress;
std::atomic<uint64_t> _connectedAt; std::atomic<uint64_t> _connectedAt;
std::atomic<uint64_t> _incomingMessages; std::atomic<uint64_t> _incomingMessages;
std::atomic<uint64_t> _outgoingMessages; std::atomic<uint64_t> _outgoingMessages;
std::atomic<uint8_t> _encoding; std::atomic<uint8_t> _encoding;
std::atomic<bool> _authenticationRequired; std::atomic<bool> _authenticationRequired;
std::mutex _secretMutex; std::mutex _secretMutex;
std::string _secret; std::string _secret;
std::mutex _challengeMutex; std::mutex _challengeMutex;
std::string _challenge; std::string _challenge;
std::atomic<uint8_t> _rpcVersion; std::atomic<uint8_t> _rpcVersion;
std::atomic<bool> _isIdentified; std::atomic<bool> _isIdentified;
std::atomic<uint64_t> _eventSubscriptions; std::atomic<uint64_t> _eventSubscriptions;
}; };

View File

@ -20,8 +20,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once #pragma once
namespace WebSocketCloseCode { namespace WebSocketCloseCode {
enum WebSocketCloseCode { enum WebSocketCloseCode {
/** /**
* For internal use only to tell the request handler not to perform any close action. * For internal use only to tell the request handler not to perform any close action.
* *
* @enumIdentifier DontClose * @enumIdentifier DontClose
@ -31,8 +31,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
DontClose = 0, DontClose = 0,
/** /**
* Unknown reason, should never be used. * Unknown reason, should never be used.
* *
* @enumIdentifier UnknownReason * @enumIdentifier UnknownReason
@ -42,8 +42,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
UnknownReason = 4000, UnknownReason = 4000,
/** /**
* The server was unable to decode the incoming websocket message. * The server was unable to decode the incoming websocket message.
* *
* @enumIdentifier MessageDecodeError * @enumIdentifier MessageDecodeError
@ -53,8 +53,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
MessageDecodeError = 4002, MessageDecodeError = 4002,
/** /**
* A data field is required but missing from the payload. * A data field is required but missing from the payload.
* *
* @enumIdentifier MissingDataField * @enumIdentifier MissingDataField
@ -64,8 +64,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
MissingDataField = 4003, MissingDataField = 4003,
/** /**
* A data field's value type is invalid. * A data field's value type is invalid.
* *
* @enumIdentifier InvalidDataFieldType * @enumIdentifier InvalidDataFieldType
@ -75,8 +75,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InvalidDataFieldType = 4004, InvalidDataFieldType = 4004,
/** /**
* A data field's value is invalid. * A data field's value is invalid.
* *
* @enumIdentifier InvalidDataFieldValue * @enumIdentifier InvalidDataFieldValue
@ -86,8 +86,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
InvalidDataFieldValue = 4005, InvalidDataFieldValue = 4005,
/** /**
* The specified `op` was invalid or missing. * The specified `op` was invalid or missing.
* *
* @enumIdentifier UnknownOpCode * @enumIdentifier UnknownOpCode
@ -97,8 +97,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
UnknownOpCode = 4006, UnknownOpCode = 4006,
/** /**
* The client sent a websocket message without first sending `Identify` message. * The client sent a websocket message without first sending `Identify` message.
* *
* @enumIdentifier NotIdentified * @enumIdentifier NotIdentified
@ -108,8 +108,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
NotIdentified = 4007, NotIdentified = 4007,
/** /**
* The client sent an `Identify` message while already identified. * The client sent an `Identify` message while already identified.
* *
* Note: Once a client has identified, only `Reidentify` may be used to change session parameters. * Note: Once a client has identified, only `Reidentify` may be used to change session parameters.
@ -121,8 +121,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
AlreadyIdentified = 4008, AlreadyIdentified = 4008,
/** /**
* The authentication attempt (via `Identify`) failed. * The authentication attempt (via `Identify`) failed.
* *
* @enumIdentifier AuthenticationFailed * @enumIdentifier AuthenticationFailed
@ -132,8 +132,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
AuthenticationFailed = 4009, AuthenticationFailed = 4009,
/** /**
* The server detected the usage of an old version of the obs-websocket RPC protocol. * The server detected the usage of an old version of the obs-websocket RPC protocol.
* *
* @enumIdentifier UnsupportedRpcVersion * @enumIdentifier UnsupportedRpcVersion
@ -143,8 +143,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
UnsupportedRpcVersion = 4010, UnsupportedRpcVersion = 4010,
/** /**
* The websocket session has been invalidated by the obs-websocket server. * The websocket session has been invalidated by the obs-websocket server.
* *
* Note: This is the code used by the `Kick` button in the UI Session List. If you receive this code, you must not automatically reconnect. * Note: This is the code used by the `Kick` button in the UI Session List. If you receive this code, you must not automatically reconnect.
@ -156,8 +156,8 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
SessionInvalidated = 4011, SessionInvalidated = 4011,
/** /**
* A requested feature is not supported due to hardware/software limitations. * A requested feature is not supported due to hardware/software limitations.
* *
* @enumIdentifier UnsupportedFeature * @enumIdentifier UnsupportedFeature
@ -167,6 +167,6 @@ namespace WebSocketCloseCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
UnsupportedFeature = 4012, UnsupportedFeature = 4012,
}; };
} }

View File

@ -20,8 +20,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once #pragma once
namespace WebSocketOpCode { namespace WebSocketOpCode {
enum WebSocketOpCode: uint8_t { enum WebSocketOpCode : uint8_t {
/** /**
* The initial message sent by obs-websocket to newly connected clients. * The initial message sent by obs-websocket to newly connected clients.
* *
* @enumIdentifier Hello * @enumIdentifier Hello
@ -31,8 +31,8 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Hello = 0, Hello = 0,
/** /**
* The message sent by a newly connected client to obs-websocket in response to a `Hello`. * The message sent by a newly connected client to obs-websocket in response to a `Hello`.
* *
* @enumIdentifier Identify * @enumIdentifier Identify
@ -42,8 +42,8 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Identify = 1, Identify = 1,
/** /**
* The response sent by obs-websocket to a client after it has successfully identified with obs-websocket. * The response sent by obs-websocket to a client after it has successfully identified with obs-websocket.
* *
* @enumIdentifier Identified * @enumIdentifier Identified
@ -53,8 +53,8 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Identified = 2, Identified = 2,
/** /**
* The message sent by an already-identified client to update identification parameters. * The message sent by an already-identified client to update identification parameters.
* *
* @enumIdentifier Reidentify * @enumIdentifier Reidentify
@ -64,8 +64,8 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Reidentify = 3, Reidentify = 3,
/** /**
* The message sent by obs-websocket containing an event payload. * The message sent by obs-websocket containing an event payload.
* *
* @enumIdentifier Event * @enumIdentifier Event
@ -75,8 +75,8 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Event = 5, Event = 5,
/** /**
* The message sent by a client to obs-websocket to perform a request. * The message sent by a client to obs-websocket to perform a request.
* *
* @enumIdentifier Request * @enumIdentifier Request
@ -86,8 +86,8 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
Request = 6, Request = 6,
/** /**
* The message sent by obs-websocket in response to a particular request from a client. * The message sent by obs-websocket in response to a particular request from a client.
* *
* @enumIdentifier RequestResponse * @enumIdentifier RequestResponse
@ -97,8 +97,8 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
RequestResponse = 7, RequestResponse = 7,
/** /**
* The message sent by a client to obs-websocket to perform a batch of requests. * The message sent by a client to obs-websocket to perform a batch of requests.
* *
* @enumIdentifier RequestBatch * @enumIdentifier RequestBatch
@ -108,8 +108,8 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
RequestBatch = 8, RequestBatch = 8,
/** /**
* The message sent by obs-websocket in response to a particular batch of requests from a client. * The message sent by obs-websocket in response to a particular batch of requests from a client.
* *
* @enumIdentifier RequestBatchResponse * @enumIdentifier RequestBatchResponse
@ -119,11 +119,11 @@ namespace WebSocketOpCode {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
RequestBatchResponse = 9, RequestBatchResponse = 9,
}; };
inline bool IsValid(uint8_t opCode) inline bool IsValid(uint8_t opCode)
{ {
return opCode >= Hello && opCode <= RequestBatchResponse; return opCode >= Hello && opCode <= RequestBatchResponse;
} }
} }