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

@ -34,33 +34,43 @@ bool obs_module_load(void)
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 *priv_data);
void obs_module_post_load(void) void obs_module_post_load(void)
{ {
vendor = obs_websocket_register_vendor("api_example_plugin"); vendor = obs_websocket_register_vendor("api_example_plugin");
if (!vendor) { if (!vendor) {
blog(LOG_ERROR, "Vendor registration failed! (obs-websocket should have logged something if installed properly.)"); blog(LOG_ERROR,
"Vendor registration failed! (obs-websocket should have logged something if installed properly.)");
return; return;
} }
if (!obs_websocket_vendor_register_request(vendor, "example_request", example_request_cb, NULL)) if (!obs_websocket_vendor_register_request(vendor, "example_request",
blog(LOG_ERROR, "Failed to register `example_request` request with obs-websocket."); example_request_cb, NULL))
blog(LOG_ERROR,
"Failed to register `example_request` request with obs-websocket.");
uint api_version = obs_websocket_get_api_version(); uint api_version = obs_websocket_get_api_version();
if (api_version == 0) { if (api_version == 0) {
blog(LOG_ERROR, "Unable to fetch obs-websocket plugin API version."); blog(LOG_ERROR,
"Unable to fetch obs-websocket plugin API version.");
return; return;
} else if (api_version == 1) { } else if (api_version == 1) {
blog(LOG_WARNING, "Unsupported obs-websocket plugin API version for calling requests."); blog(LOG_WARNING,
"Unsupported obs-websocket plugin API version for calling requests.");
return; return;
} }
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_ERROR, "Failed to call GetVersion due to obs-websocket not being installed."); blog(LOG_ERROR,
"Failed to call GetVersion due to obs-websocket not being installed.");
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); 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);
} }
@ -69,7 +79,8 @@ void obs_module_unload(void)
blog(LOG_INFO, "Example obs-websocket-api plugin unloaded!"); 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) 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")) if (obs_data_has_user_value(request_data, "ping"))
obs_data_set_bool(response_data, "pong", true); obs_data_set_bool(response_data, "pong", true);

View File

@ -29,7 +29,8 @@ 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,7 +56,8 @@ 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,
"Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph"); proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
calldata_free(&cd); calldata_free(&cd);
@ -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,8 +38,8 @@ 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),
@ -62,13 +62,20 @@ void Config::Load()
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,7 +134,8 @@ 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;
} }
} }
@ -129,16 +148,23 @@ void Config::Save()
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);
@ -148,17 +174,25 @@ 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()

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,7 +15,8 @@ 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;
} }
@ -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);
@ -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,7 +154,9 @@ 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();
} }
@ -139,8 +166,10 @@ 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,7 +177,9 @@ 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();
} }
@ -157,7 +188,9 @@ void WebSocketApi::vendor_register_cb(void *priv_data, calldata_t *cd)
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));
@ -171,14 +204,19 @@ 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();
} }
@ -187,13 +225,17 @@ void WebSocketApi::vendor_request_register_cb(void *, calldata_t *cd)
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,21 +247,28 @@ 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();
} }
@ -233,14 +282,19 @@ void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd)
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();
} }

View File

@ -17,7 +17,8 @@ class WebSocketApi {
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::shared_mutex _mutex;
@ -30,14 +31,18 @@ class WebSocketApi {
void SetEventCallback(EventCallback cb); void SetEventCallback(EventCallback cb);
enum RequestReturnCode PerformVendorRequest(std::string vendorName, std::string requestName, obs_data_t *requestData, obs_data_t *responseData); 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_ph_cb(void *priv_data, calldata_t *cd);
static void get_api_version(void *, calldata_t *cd); static void get_api_version(void *, calldata_t *cd);
static void call_request(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_register_cb(void *priv_data, calldata_t *cd);
static void vendor_request_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_request_unregister_cb(void *priv_data,
calldata_t *cd);
static void vendor_event_emit_cb(void *priv_data, calldata_t *cd); static void vendor_event_emit_cb(void *priv_data, calldata_t *cd);
private: private:

View File

@ -19,8 +19,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #include "EventHandler.h"
EventHandler::EventHandler() : EventHandler::EventHandler()
_obsLoaded(false), : _obsLoaded(false),
_inputVolumeMetersRef(0), _inputVolumeMetersRef(0),
_inputActiveStateChangedRef(0), _inputActiveStateChangedRef(0),
_inputShowStateChangedRef(0), _inputShowStateChangedRef(0),
@ -32,12 +32,17 @@ EventHandler::EventHandler() :
signal_handler_t *coreSignalHandler = obs_get_signal_handler(); signal_handler_t *coreSignalHandler = obs_get_signal_handler();
if (coreSignalHandler) { if (coreSignalHandler) {
signal_handler_connect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this); signal_handler_connect(coreSignalHandler, "source_create",
signal_handler_connect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this); SourceCreatedMultiHandler, this);
signal_handler_connect(coreSignalHandler, "source_remove", SourceRemovedMultiHandler, this); signal_handler_connect(coreSignalHandler, "source_destroy",
signal_handler_connect(coreSignalHandler, "source_rename", SourceRenamedMultiHandler, this); SourceDestroyedMultiHandler, this);
signal_handler_connect(coreSignalHandler, "source_remove",
SourceRemovedMultiHandler, this);
signal_handler_connect(coreSignalHandler, "source_rename",
SourceRenamedMultiHandler, this);
} else { } else {
blog(LOG_ERROR, "[EventHandler::EventHandler] Unable to get libobs signal handler!"); blog(LOG_ERROR,
"[EventHandler::EventHandler] Unable to get libobs signal handler!");
} }
blog_debug("[EventHandler::EventHandler] Finished."); blog_debug("[EventHandler::EventHandler] Finished.");
@ -51,12 +56,17 @@ EventHandler::~EventHandler()
signal_handler_t *coreSignalHandler = obs_get_signal_handler(); signal_handler_t *coreSignalHandler = obs_get_signal_handler();
if (coreSignalHandler) { if (coreSignalHandler) {
signal_handler_disconnect(coreSignalHandler, "source_create", SourceCreatedMultiHandler, this); signal_handler_disconnect(coreSignalHandler, "source_create",
signal_handler_disconnect(coreSignalHandler, "source_destroy", SourceDestroyedMultiHandler, this); SourceCreatedMultiHandler, this);
signal_handler_disconnect(coreSignalHandler, "source_remove", SourceRemovedMultiHandler, this); signal_handler_disconnect(coreSignalHandler, "source_destroy",
signal_handler_disconnect(coreSignalHandler, "source_rename", SourceRenamedMultiHandler, this); SourceDestroyedMultiHandler, this);
signal_handler_disconnect(coreSignalHandler, "source_remove",
SourceRemovedMultiHandler, this);
signal_handler_disconnect(coreSignalHandler, "source_rename",
SourceRenamedMultiHandler, this);
} else { } else {
blog(LOG_ERROR, "[EventHandler::~EventHandler] Unable to get libobs signal handler!"); blog(LOG_ERROR,
"[EventHandler::~EventHandler] Unable to get libobs signal handler!");
} }
blog_debug("[EventHandler::~EventHandler] Finished."); blog_debug("[EventHandler::~EventHandler] Finished.");
@ -78,16 +88,25 @@ void EventHandler::ProcessSubscription(uint64_t eventSubscriptions)
if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0) { if ((eventSubscriptions & EventSubscription::InputVolumeMeters) != 0) {
if (_inputVolumeMetersRef.fetch_add(1) == 0) { if (_inputVolumeMetersRef.fetch_add(1) == 0) {
if (_inputVolumeMetersHandler) if (_inputVolumeMetersHandler)
blog(LOG_WARNING, "[EventHandler::ProcessSubscription] Input volume meter handler already exists!"); blog(LOG_WARNING,
"[EventHandler::ProcessSubscription] Input volume meter handler already exists!");
else else
_inputVolumeMetersHandler = std::make_unique<Utils::Obs::VolumeMeter::Handler>(std::bind(&EventHandler::HandleInputVolumeMeters, this, std::placeholders::_1)); _inputVolumeMetersHandler = std::make_unique<
Utils::Obs::VolumeMeter::Handler>(
std::bind(
&EventHandler::
HandleInputVolumeMeters,
this, std::placeholders::_1));
} }
} }
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0) if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) !=
0)
_inputActiveStateChangedRef++; _inputActiveStateChangedRef++;
if ((eventSubscriptions & EventSubscription::InputShowStateChanged) != 0) if ((eventSubscriptions & EventSubscription::InputShowStateChanged) !=
0)
_inputShowStateChangedRef++; _inputShowStateChangedRef++;
if ((eventSubscriptions & EventSubscription::SceneItemTransformChanged) != 0) if ((eventSubscriptions &
EventSubscription::SceneItemTransformChanged) != 0)
_sceneItemTransformChangedRef++; _sceneItemTransformChangedRef++;
} }
@ -98,16 +117,21 @@ void EventHandler::ProcessUnsubscription(uint64_t eventSubscriptions)
if (_inputVolumeMetersRef.fetch_sub(1) == 1) if (_inputVolumeMetersRef.fetch_sub(1) == 1)
_inputVolumeMetersHandler.reset(); _inputVolumeMetersHandler.reset();
} }
if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) != 0) if ((eventSubscriptions & EventSubscription::InputActiveStateChanged) !=
0)
_inputActiveStateChangedRef--; _inputActiveStateChangedRef--;
if ((eventSubscriptions & EventSubscription::InputShowStateChanged) != 0) if ((eventSubscriptions & EventSubscription::InputShowStateChanged) !=
0)
_inputShowStateChangedRef--; _inputShowStateChangedRef--;
if ((eventSubscriptions & EventSubscription::SceneItemTransformChanged) != 0) if ((eventSubscriptions &
EventSubscription::SceneItemTransformChanged) != 0)
_sceneItemTransformChangedRef--; _sceneItemTransformChangedRef--;
} }
// Function required in order to use default arguments // Function required in order to use default arguments
void EventHandler::BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData, uint8_t rpcVersion) void EventHandler::BroadcastEvent(uint64_t requiredIntent,
std::string eventType, json eventData,
uint8_t rpcVersion)
{ {
if (!_broadcastCallback) if (!_broadcastCallback)
return; return;
@ -116,7 +140,8 @@ void EventHandler::BroadcastEvent(uint64_t requiredIntent, std::string eventType
} }
// Connect source signals for Inputs, Scenes, and Transitions. Filters are automatically connected. // Connect source signals for Inputs, Scenes, and Transitions. Filters are automatically connected.
void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inputs and scenes void EventHandler::ConnectSourceSignals(
obs_source_t *source) // Applies to inputs and scenes
{ {
if (!source || obs_source_removed(source)) if (!source || obs_source_removed(source))
return; return;
@ -130,43 +155,74 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu
// Inputs // Inputs
if (sourceType == OBS_SOURCE_TYPE_INPUT) { if (sourceType == OBS_SOURCE_TYPE_INPUT) {
signal_handler_connect(sh, "activate", HandleInputActiveStateChanged, this); signal_handler_connect(sh, "activate",
signal_handler_connect(sh, "deactivate", HandleInputActiveStateChanged, this); HandleInputActiveStateChanged, this);
signal_handler_connect(sh, "show", HandleInputShowStateChanged, this); signal_handler_connect(sh, "deactivate",
signal_handler_connect(sh, "hide", HandleInputShowStateChanged, this); HandleInputActiveStateChanged, this);
signal_handler_connect(sh, "mute", HandleInputMuteStateChanged, this); signal_handler_connect(sh, "show", HandleInputShowStateChanged,
signal_handler_connect(sh, "volume", HandleInputVolumeChanged, this); this);
signal_handler_connect(sh, "audio_balance", HandleInputAudioBalanceChanged, this); signal_handler_connect(sh, "hide", HandleInputShowStateChanged,
signal_handler_connect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this); this);
signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, this); signal_handler_connect(sh, "mute", HandleInputMuteStateChanged,
signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this); this);
signal_handler_connect(sh, "media_started", HandleMediaInputPlaybackStarted, this); signal_handler_connect(sh, "volume", HandleInputVolumeChanged,
signal_handler_connect(sh, "media_ended", HandleMediaInputPlaybackEnded, this); this);
signal_handler_connect(sh, "media_pause", SourceMediaPauseMultiHandler, this); signal_handler_connect(sh, "audio_balance",
signal_handler_connect(sh, "media_play", SourceMediaPlayMultiHandler, this); HandleInputAudioBalanceChanged, this);
signal_handler_connect(sh, "media_restart", SourceMediaRestartMultiHandler, this); signal_handler_connect(sh, "audio_sync",
signal_handler_connect(sh, "media_stopped", SourceMediaStopMultiHandler, this); HandleInputAudioSyncOffsetChanged, this);
signal_handler_connect(sh, "media_next", SourceMediaNextMultiHandler, this); signal_handler_connect(sh, "audio_mixers",
signal_handler_connect(sh, "media_previous", SourceMediaPreviousMultiHandler, this); HandleInputAudioTracksChanged, this);
signal_handler_connect(sh, "audio_monitoring",
HandleInputAudioMonitorTypeChanged,
this);
signal_handler_connect(sh, "media_started",
HandleMediaInputPlaybackStarted, this);
signal_handler_connect(sh, "media_ended",
HandleMediaInputPlaybackEnded, this);
signal_handler_connect(sh, "media_pause",
SourceMediaPauseMultiHandler, this);
signal_handler_connect(sh, "media_play",
SourceMediaPlayMultiHandler, this);
signal_handler_connect(sh, "media_restart",
SourceMediaRestartMultiHandler, this);
signal_handler_connect(sh, "media_stopped",
SourceMediaStopMultiHandler, this);
signal_handler_connect(sh, "media_next",
SourceMediaNextMultiHandler, this);
signal_handler_connect(sh, "media_previous",
SourceMediaPreviousMultiHandler, this);
} }
// Scenes // Scenes
if (sourceType == OBS_SOURCE_TYPE_SCENE) { if (sourceType == OBS_SOURCE_TYPE_SCENE) {
signal_handler_connect(sh, "item_add", HandleSceneItemCreated, this); signal_handler_connect(sh, "item_add", HandleSceneItemCreated,
signal_handler_connect(sh, "item_remove", HandleSceneItemRemoved, this); this);
signal_handler_connect(sh, "reorder", HandleSceneItemListReindexed, this); signal_handler_connect(sh, "item_remove",
signal_handler_connect(sh, "item_visible", HandleSceneItemEnableStateChanged, this); HandleSceneItemRemoved, this);
signal_handler_connect(sh, "item_locked", HandleSceneItemLockStateChanged, this); signal_handler_connect(sh, "reorder",
signal_handler_connect(sh, "item_select", HandleSceneItemSelected, this); HandleSceneItemListReindexed, this);
signal_handler_connect(sh, "item_transform", HandleSceneItemTransformChanged, this); signal_handler_connect(sh, "item_visible",
HandleSceneItemEnableStateChanged, this);
signal_handler_connect(sh, "item_locked",
HandleSceneItemLockStateChanged, this);
signal_handler_connect(sh, "item_select",
HandleSceneItemSelected, this);
signal_handler_connect(sh, "item_transform",
HandleSceneItemTransformChanged, this);
} }
// Scenes and Inputs // Scenes and Inputs
if (sourceType == OBS_SOURCE_TYPE_INPUT || sourceType == OBS_SOURCE_TYPE_SCENE) { if (sourceType == OBS_SOURCE_TYPE_INPUT ||
signal_handler_connect(sh, "reorder_filters", HandleSourceFilterListReindexed, this); sourceType == OBS_SOURCE_TYPE_SCENE) {
signal_handler_connect(sh, "filter_add", FilterAddMultiHandler, this); signal_handler_connect(sh, "reorder_filters",
signal_handler_connect(sh, "filter_remove", FilterRemoveMultiHandler, this); HandleSourceFilterListReindexed, this);
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){ signal_handler_connect(sh, "filter_add", FilterAddMultiHandler,
this);
signal_handler_connect(sh, "filter_remove",
FilterRemoveMultiHandler, this);
auto enumFilters = [](obs_source_t *, obs_source_t *filter,
void *param) {
auto eventHandler = static_cast<EventHandler *>(param); auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->ConnectSourceSignals(filter); eventHandler->ConnectSourceSignals(filter);
}; };
@ -175,15 +231,21 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu
// Transitions // Transitions
if (sourceType == OBS_SOURCE_TYPE_TRANSITION) { if (sourceType == OBS_SOURCE_TYPE_TRANSITION) {
signal_handler_connect(sh, "transition_start", HandleSceneTransitionStarted, this); signal_handler_connect(sh, "transition_start",
signal_handler_connect(sh, "transition_stop", HandleSceneTransitionEnded, this); HandleSceneTransitionStarted, this);
signal_handler_connect(sh, "transition_video_stop", HandleSceneTransitionVideoEnded, this); signal_handler_connect(sh, "transition_stop",
HandleSceneTransitionEnded, this);
signal_handler_connect(sh, "transition_video_stop",
HandleSceneTransitionVideoEnded, this);
} }
// Filters // Filters
if (sourceType == OBS_SOURCE_TYPE_FILTER) { if (sourceType == OBS_SOURCE_TYPE_FILTER) {
signal_handler_connect(sh, "enable", HandleSourceFilterEnableStateChanged, this); signal_handler_connect(sh, "enable",
signal_handler_connect(sh, "rename", HandleSourceFilterNameChanged, this); HandleSourceFilterEnableStateChanged,
this);
signal_handler_connect(sh, "rename",
HandleSourceFilterNameChanged, this);
} }
} }
@ -199,43 +261,81 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
// Inputs // Inputs
if (sourceType == OBS_SOURCE_TYPE_INPUT) { if (sourceType == OBS_SOURCE_TYPE_INPUT) {
signal_handler_disconnect(sh, "activate", HandleInputActiveStateChanged, this); signal_handler_disconnect(sh, "activate",
signal_handler_disconnect(sh, "deactivate", HandleInputActiveStateChanged, this); HandleInputActiveStateChanged, this);
signal_handler_disconnect(sh, "show", HandleInputShowStateChanged, this); signal_handler_disconnect(sh, "deactivate",
signal_handler_disconnect(sh, "hide", HandleInputShowStateChanged, this); HandleInputActiveStateChanged, this);
signal_handler_disconnect(sh, "mute", HandleInputMuteStateChanged, this); signal_handler_disconnect(sh, "show",
signal_handler_disconnect(sh, "volume", HandleInputVolumeChanged, this); HandleInputShowStateChanged, this);
signal_handler_disconnect(sh, "audio_balance", HandleInputAudioBalanceChanged, this); signal_handler_disconnect(sh, "hide",
signal_handler_disconnect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this); HandleInputShowStateChanged, this);
signal_handler_disconnect(sh, "audio_mixers", HandleInputAudioTracksChanged, this); signal_handler_disconnect(sh, "mute",
signal_handler_disconnect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this); HandleInputMuteStateChanged, this);
signal_handler_disconnect(sh, "media_started", HandleMediaInputPlaybackStarted, this); signal_handler_disconnect(sh, "volume",
signal_handler_disconnect(sh, "media_ended", HandleMediaInputPlaybackEnded, this); HandleInputVolumeChanged, this);
signal_handler_disconnect(sh, "media_pause", SourceMediaPauseMultiHandler, this); signal_handler_disconnect(sh, "audio_balance",
signal_handler_disconnect(sh, "media_play", SourceMediaPlayMultiHandler, this); HandleInputAudioBalanceChanged, this);
signal_handler_disconnect(sh, "media_restart", SourceMediaRestartMultiHandler, this); signal_handler_disconnect(sh, "audio_sync",
signal_handler_disconnect(sh, "media_stopped", SourceMediaStopMultiHandler, this); HandleInputAudioSyncOffsetChanged,
signal_handler_disconnect(sh, "media_next", SourceMediaNextMultiHandler, this); this);
signal_handler_disconnect(sh, "media_previous", SourceMediaPreviousMultiHandler, this); signal_handler_disconnect(sh, "audio_mixers",
HandleInputAudioTracksChanged, this);
signal_handler_disconnect(sh, "audio_monitoring",
HandleInputAudioMonitorTypeChanged,
this);
signal_handler_disconnect(sh, "media_started",
HandleMediaInputPlaybackStarted,
this);
signal_handler_disconnect(sh, "media_ended",
HandleMediaInputPlaybackEnded, this);
signal_handler_disconnect(sh, "media_pause",
SourceMediaPauseMultiHandler, this);
signal_handler_disconnect(sh, "media_play",
SourceMediaPlayMultiHandler, this);
signal_handler_disconnect(sh, "media_restart",
SourceMediaRestartMultiHandler, this);
signal_handler_disconnect(sh, "media_stopped",
SourceMediaStopMultiHandler, this);
signal_handler_disconnect(sh, "media_next",
SourceMediaNextMultiHandler, this);
signal_handler_disconnect(sh, "media_previous",
SourceMediaPreviousMultiHandler,
this);
} }
// Scenes // Scenes
if (sourceType == OBS_SOURCE_TYPE_SCENE) { if (sourceType == OBS_SOURCE_TYPE_SCENE) {
signal_handler_disconnect(sh, "item_add", HandleSceneItemCreated, this); signal_handler_disconnect(sh, "item_add",
signal_handler_disconnect(sh, "item_remove", HandleSceneItemRemoved, this); HandleSceneItemCreated, this);
signal_handler_disconnect(sh, "reorder", HandleSceneItemListReindexed, this); signal_handler_disconnect(sh, "item_remove",
signal_handler_disconnect(sh, "item_visible", HandleSceneItemEnableStateChanged, this); HandleSceneItemRemoved, this);
signal_handler_disconnect(sh, "item_locked", HandleSceneItemLockStateChanged, this); signal_handler_disconnect(sh, "reorder",
signal_handler_disconnect(sh, "item_select", HandleSceneItemSelected, this); HandleSceneItemListReindexed, this);
signal_handler_disconnect(sh, "item_transform", HandleSceneItemTransformChanged, this); signal_handler_disconnect(sh, "item_visible",
HandleSceneItemEnableStateChanged,
this);
signal_handler_disconnect(sh, "item_locked",
HandleSceneItemLockStateChanged,
this);
signal_handler_disconnect(sh, "item_select",
HandleSceneItemSelected, this);
signal_handler_disconnect(sh, "item_transform",
HandleSceneItemTransformChanged,
this);
} }
// Inputs and Scenes // Inputs and Scenes
if (sourceType == OBS_SOURCE_TYPE_INPUT || sourceType == OBS_SOURCE_TYPE_SCENE) { if (sourceType == OBS_SOURCE_TYPE_INPUT ||
signal_handler_disconnect(sh, "reorder_filters", HandleSourceFilterListReindexed, this); sourceType == OBS_SOURCE_TYPE_SCENE) {
signal_handler_disconnect(sh, "filter_add", FilterAddMultiHandler, this); signal_handler_disconnect(sh, "reorder_filters",
signal_handler_disconnect(sh, "filter_remove", FilterRemoveMultiHandler, this); HandleSourceFilterListReindexed,
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){ this);
signal_handler_disconnect(sh, "filter_add",
FilterAddMultiHandler, this);
signal_handler_disconnect(sh, "filter_remove",
FilterRemoveMultiHandler, this);
auto enumFilters = [](obs_source_t *, obs_source_t *filter,
void *param) {
auto eventHandler = static_cast<EventHandler *>(param); auto eventHandler = static_cast<EventHandler *>(param);
eventHandler->DisconnectSourceSignals(filter); eventHandler->DisconnectSourceSignals(filter);
}; };
@ -244,29 +344,39 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
// Transitions // Transitions
if (sourceType == OBS_SOURCE_TYPE_TRANSITION) { if (sourceType == OBS_SOURCE_TYPE_TRANSITION) {
signal_handler_disconnect(sh, "transition_start", HandleSceneTransitionStarted, this); signal_handler_disconnect(sh, "transition_start",
signal_handler_disconnect(sh, "transition_stop", HandleSceneTransitionEnded, this); HandleSceneTransitionStarted, this);
signal_handler_disconnect(sh, "transition_video_stop", HandleSceneTransitionVideoEnded, this); signal_handler_disconnect(sh, "transition_stop",
HandleSceneTransitionEnded, this);
signal_handler_disconnect(sh, "transition_video_stop",
HandleSceneTransitionVideoEnded,
this);
} }
// Filters // Filters
if (sourceType == OBS_SOURCE_TYPE_FILTER) { if (sourceType == OBS_SOURCE_TYPE_FILTER) {
signal_handler_disconnect(sh, "enable", HandleSourceFilterEnableStateChanged, this); signal_handler_disconnect(sh, "enable",
signal_handler_disconnect(sh, "rename", HandleSourceFilterNameChanged, this); HandleSourceFilterEnableStateChanged,
this);
signal_handler_disconnect(sh, "rename",
HandleSourceFilterNameChanged, this);
} }
} }
void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_data) void EventHandler::OnFrontendEvent(enum obs_frontend_event event,
void *private_data)
{ {
auto eventHandler = static_cast<EventHandler *>(private_data); auto eventHandler = static_cast<EventHandler *>(private_data);
if (!eventHandler->_obsLoaded.load() && event != OBS_FRONTEND_EVENT_FINISHED_LOADING) if (!eventHandler->_obsLoaded.load() &&
event != OBS_FRONTEND_EVENT_FINISHED_LOADING)
return; return;
switch (event) { switch (event) {
// General // General
case OBS_FRONTEND_EVENT_FINISHED_LOADING: case OBS_FRONTEND_EVENT_FINISHED_LOADING:
blog_debug("[EventHandler::OnFrontendEvent] OBS has finished loading. Connecting final handlers and enabling events..."); blog_debug(
"[EventHandler::OnFrontendEvent] OBS has finished loading. Connecting final handlers and enabling events...");
// Connect source signals and enable events only after OBS has fully loaded (to reduce extra logging). // Connect source signals and enable events only after OBS has fully loaded (to reduce extra logging).
eventHandler->_obsLoaded.store(true); eventHandler->_obsLoaded.store(true);
@ -274,8 +384,10 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()` // In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()`
// Enumerate inputs and connect each one // Enumerate inputs and connect each one
{ {
auto enumInputs = [](void *param, obs_source_t *source) { auto enumInputs = [](void *param,
auto eventHandler = static_cast<EventHandler*>(param); obs_source_t *source) {
auto eventHandler =
static_cast<EventHandler *>(param);
eventHandler->ConnectSourceSignals(source); eventHandler->ConnectSourceSignals(source);
return true; return true;
}; };
@ -284,8 +396,10 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Enumerate scenes and connect each one // Enumerate scenes and connect each one
{ {
auto enumScenes = [](void *param, obs_source_t *source) { auto enumScenes = [](void *param,
auto eventHandler = static_cast<EventHandler*>(param); obs_source_t *source) {
auto eventHandler =
static_cast<EventHandler *>(param);
eventHandler->ConnectSourceSignals(source); eventHandler->ConnectSourceSignals(source);
return true; return true;
}; };
@ -297,7 +411,8 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
obs_frontend_source_list transitions = {}; obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions); obs_frontend_get_transitions(&transitions);
for (size_t i = 0; i < transitions.sources.num; i++) { for (size_t i = 0; i < transitions.sources.num; i++) {
obs_source_t* transition = transitions.sources.array[i]; obs_source_t *transition =
transitions.sources.array[i];
eventHandler->ConnectSourceSignals(transition); eventHandler->ConnectSourceSignals(transition);
} }
obs_frontend_source_list_free(&transitions); obs_frontend_source_list_free(&transitions);
@ -312,15 +427,18 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
case OBS_FRONTEND_EVENT_EXIT: case OBS_FRONTEND_EVENT_EXIT:
eventHandler->HandleExitStarted(); eventHandler->HandleExitStarted();
blog_debug("[EventHandler::OnFrontendEvent] OBS is unloading. Disabling events..."); blog_debug(
"[EventHandler::OnFrontendEvent] OBS is unloading. Disabling events...");
// Disconnect source signals and disable events when OBS starts unloading (to reduce extra logging). // Disconnect source signals and disable events when OBS starts unloading (to reduce extra logging).
eventHandler->_obsLoaded.store(false); eventHandler->_obsLoaded.store(false);
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()` // In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()`
// Enumerate inputs and disconnect each one // Enumerate inputs and disconnect each one
{ {
auto enumInputs = [](void *param, obs_source_t *source) { auto enumInputs = [](void *param,
auto eventHandler = static_cast<EventHandler*>(param); obs_source_t *source) {
auto eventHandler =
static_cast<EventHandler *>(param);
eventHandler->DisconnectSourceSignals(source); eventHandler->DisconnectSourceSignals(source);
return true; return true;
}; };
@ -329,8 +447,10 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Enumerate scenes and disconnect each one // Enumerate scenes and disconnect each one
{ {
auto enumScenes = [](void *param, obs_source_t *source) { auto enumScenes = [](void *param,
auto eventHandler = static_cast<EventHandler*>(param); obs_source_t *source) {
auto eventHandler =
static_cast<EventHandler *>(param);
eventHandler->DisconnectSourceSignals(source); eventHandler->DisconnectSourceSignals(source);
return true; return true;
}; };
@ -342,8 +462,10 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
obs_frontend_source_list transitions = {}; obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions); obs_frontend_get_transitions(&transitions);
for (size_t i = 0; i < transitions.sources.num; i++) { for (size_t i = 0; i < transitions.sources.num; i++) {
obs_source_t* transition = transitions.sources.array[i]; obs_source_t *transition =
eventHandler->DisconnectSourceSignals(transition); transitions.sources.array[i];
eventHandler->DisconnectSourceSignals(
transition);
} }
obs_frontend_source_list_free(&transitions); obs_frontend_source_list_free(&transitions);
} }
@ -359,8 +481,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
break; break;
// Config // Config
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING: case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING: {
{
obs_frontend_source_list transitions = {}; obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions); obs_frontend_get_transitions(&transitions);
for (size_t i = 0; i < transitions.sources.num; i++) { for (size_t i = 0; i < transitions.sources.num; i++) {
@ -371,8 +492,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
} }
eventHandler->HandleCurrentSceneCollectionChanging(); eventHandler->HandleCurrentSceneCollectionChanging();
break; break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: {
{
obs_frontend_source_list transitions = {}; obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions); obs_frontend_get_transitions(&transitions);
for (size_t i = 0; i < transitions.sources.num; i++) { for (size_t i = 0; i < transitions.sources.num; i++) {
@ -411,8 +531,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
eventHandler->HandleCurrentSceneTransitionChanged(); eventHandler->HandleCurrentSceneTransitionChanged();
break; break;
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: {
{
obs_frontend_source_list transitions = {}; obs_frontend_source_list transitions = {};
obs_frontend_get_transitions(&transitions); obs_frontend_get_transitions(&transitions);
for (size_t i = 0; i < transitions.sources.num; i++) { for (size_t i = 0; i < transitions.sources.num; i++) {
@ -420,60 +539,75 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
eventHandler->ConnectSourceSignals(transition); eventHandler->ConnectSourceSignals(transition);
} }
obs_frontend_source_list_free(&transitions); obs_frontend_source_list_free(&transitions);
} } break;
break;
case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED: case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED:
eventHandler->HandleCurrentSceneTransitionDurationChanged(); eventHandler->HandleCurrentSceneTransitionDurationChanged();
break; break;
// Outputs // Outputs
case OBS_FRONTEND_EVENT_STREAMING_STARTING: case OBS_FRONTEND_EVENT_STREAMING_STARTING:
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING); eventHandler->HandleStreamStateChanged(
OBS_WEBSOCKET_OUTPUT_STARTING);
break; break;
case OBS_FRONTEND_EVENT_STREAMING_STARTED: case OBS_FRONTEND_EVENT_STREAMING_STARTED:
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED); eventHandler->HandleStreamStateChanged(
OBS_WEBSOCKET_OUTPUT_STARTED);
break; break;
case OBS_FRONTEND_EVENT_STREAMING_STOPPING: case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING); eventHandler->HandleStreamStateChanged(
OBS_WEBSOCKET_OUTPUT_STOPPING);
break; break;
case OBS_FRONTEND_EVENT_STREAMING_STOPPED: case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
eventHandler->HandleStreamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED); eventHandler->HandleStreamStateChanged(
OBS_WEBSOCKET_OUTPUT_STOPPED);
break; break;
case OBS_FRONTEND_EVENT_RECORDING_STARTING: case OBS_FRONTEND_EVENT_RECORDING_STARTING:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING); eventHandler->HandleRecordStateChanged(
OBS_WEBSOCKET_OUTPUT_STARTING);
break; break;
case OBS_FRONTEND_EVENT_RECORDING_STARTED: case OBS_FRONTEND_EVENT_RECORDING_STARTED:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED); eventHandler->HandleRecordStateChanged(
OBS_WEBSOCKET_OUTPUT_STARTED);
break; break;
case OBS_FRONTEND_EVENT_RECORDING_STOPPING: case OBS_FRONTEND_EVENT_RECORDING_STOPPING:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING); eventHandler->HandleRecordStateChanged(
OBS_WEBSOCKET_OUTPUT_STOPPING);
break; break;
case OBS_FRONTEND_EVENT_RECORDING_STOPPED: case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED); eventHandler->HandleRecordStateChanged(
OBS_WEBSOCKET_OUTPUT_STOPPED);
break; break;
case OBS_FRONTEND_EVENT_RECORDING_PAUSED: case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_PAUSED); eventHandler->HandleRecordStateChanged(
OBS_WEBSOCKET_OUTPUT_PAUSED);
break; break;
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED: case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
eventHandler->HandleRecordStateChanged(OBS_WEBSOCKET_OUTPUT_RESUMED); eventHandler->HandleRecordStateChanged(
OBS_WEBSOCKET_OUTPUT_RESUMED);
break; break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING: case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING:
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STARTING); eventHandler->HandleReplayBufferStateChanged(
OBS_WEBSOCKET_OUTPUT_STARTING);
break; break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED: case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED:
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED); eventHandler->HandleReplayBufferStateChanged(
OBS_WEBSOCKET_OUTPUT_STARTED);
break; break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING: case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING:
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPING); eventHandler->HandleReplayBufferStateChanged(
OBS_WEBSOCKET_OUTPUT_STOPPING);
break; break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED: case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED:
eventHandler->HandleReplayBufferStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED); eventHandler->HandleReplayBufferStateChanged(
OBS_WEBSOCKET_OUTPUT_STOPPED);
break; break;
case OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED: case OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED:
eventHandler->HandleVirtualcamStateChanged(OBS_WEBSOCKET_OUTPUT_STARTED); eventHandler->HandleVirtualcamStateChanged(
OBS_WEBSOCKET_OUTPUT_STARTED);
break; break;
case OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED: case OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED:
eventHandler->HandleVirtualcamStateChanged(OBS_WEBSOCKET_OUTPUT_STOPPED); eventHandler->HandleVirtualcamStateChanged(
OBS_WEBSOCKET_OUTPUT_STOPPED);
break; break;
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED: case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
eventHandler->HandleReplayBufferSaved(); eventHandler->HandleReplayBufferSaved();
@ -588,12 +722,14 @@ void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
switch (obs_source_get_type(source)) { switch (obs_source_get_type(source)) {
case OBS_SOURCE_TYPE_INPUT: case OBS_SOURCE_TYPE_INPUT:
eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName); eventHandler->HandleInputNameChanged(source, oldSourceName,
sourceName);
break; break;
case OBS_SOURCE_TYPE_TRANSITION: case OBS_SOURCE_TYPE_TRANSITION:
break; break;
case OBS_SOURCE_TYPE_SCENE: case OBS_SOURCE_TYPE_SCENE:
eventHandler->HandleSceneNameChanged(source, oldSourceName, sourceName); eventHandler->HandleSceneNameChanged(source, oldSourceName,
sourceName);
break; break;
default: default:
break; break;

View File

@ -29,13 +29,13 @@ 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)>
BroadcastCallback;
void SetBroadcastCallback(BroadcastCallback cb); void SetBroadcastCallback(BroadcastCallback cb);
typedef std::function<void()> ObsLoadedCallback; typedef std::function<void()> ObsLoadedCallback;
void SetObsLoadedCallback(ObsLoadedCallback cb); void SetObsLoadedCallback(ObsLoadedCallback cb);
@ -49,7 +49,8 @@ class EventHandler
std::atomic<bool> _obsLoaded; std::atomic<bool> _obsLoaded;
std::unique_ptr<Utils::Obs::VolumeMeter::Handler> _inputVolumeMetersHandler; std::unique_ptr<Utils::Obs::VolumeMeter::Handler>
_inputVolumeMetersHandler;
std::atomic<uint64_t> _inputVolumeMetersRef; std::atomic<uint64_t> _inputVolumeMetersRef;
std::atomic<uint64_t> _inputActiveStateChangedRef; std::atomic<uint64_t> _inputActiveStateChangedRef;
std::atomic<uint64_t> _inputShowStateChangedRef; std::atomic<uint64_t> _inputShowStateChangedRef;
@ -58,10 +59,12 @@ class EventHandler
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);
@ -72,11 +75,12 @@ class EventHandler
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,
calldata_t *data);
static void SourceMediaStopMultiHandler(void *param, calldata_t *data); static void SourceMediaStopMultiHandler(void *param, calldata_t *data);
static void SourceMediaNextMultiHandler(void *param, calldata_t *data); static void SourceMediaNextMultiHandler(void *param, calldata_t *data);
static void SourceMediaPreviousMultiHandler(void *param, calldata_t *data); static void SourceMediaPreviousMultiHandler(void *param,
calldata_t *data);
// General // General
void HandleExitStarted(); void HandleExitStarted();
@ -93,7 +97,9 @@ class EventHandler
// Scenes // Scenes
void HandleSceneCreated(obs_source_t *source); void HandleSceneCreated(obs_source_t *source);
void HandleSceneRemoved(obs_source_t *source); void HandleSceneRemoved(obs_source_t *source);
void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName); void HandleSceneNameChanged(obs_source_t *source,
std::string oldSceneName,
std::string sceneName);
void HandleCurrentProgramSceneChanged(); void HandleCurrentProgramSceneChanged();
void HandleCurrentPreviewSceneChanged(); void HandleCurrentPreviewSceneChanged();
void HandleSceneListChanged(); void HandleSceneListChanged();
@ -101,32 +107,67 @@ class EventHandler
// Inputs // Inputs
void HandleInputCreated(obs_source_t *source); void HandleInputCreated(obs_source_t *source);
void HandleInputRemoved(obs_source_t *source); void HandleInputRemoved(obs_source_t *source);
void HandleInputNameChanged(obs_source_t *source, std::string oldInputName, std::string inputName); void HandleInputNameChanged(obs_source_t *source,
void HandleInputVolumeMeters(std::vector<json> inputs); // AudioMeter::Handler callback std::string oldInputName,
static void HandleInputActiveStateChanged(void *param, calldata_t *data); // Direct callback std::string inputName);
static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback void HandleInputVolumeMeters(
static void HandleInputMuteStateChanged(void *param, calldata_t *data); // Direct callback std::vector<json> inputs); // AudioMeter::Handler callback
static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback static void
static void HandleInputAudioBalanceChanged(void *param, calldata_t *data); // Direct callback HandleInputActiveStateChanged(void *param,
static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback static void
static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback 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
// Transitions // Transitions
void HandleCurrentSceneTransitionChanged(); void HandleCurrentSceneTransitionChanged();
void HandleCurrentSceneTransitionDurationChanged(); void HandleCurrentSceneTransitionDurationChanged();
static void HandleSceneTransitionStarted(void *param, calldata_t *data); // Direct callback static void
static void HandleSceneTransitionEnded(void *param, calldata_t *data); // Direct callback HandleSceneTransitionStarted(void *param,
static void HandleSceneTransitionVideoEnded(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void
HandleSceneTransitionEnded(void *param,
calldata_t *data); // Direct callback
static void
HandleSceneTransitionVideoEnded(void *param,
calldata_t *data); // Direct callback
// Filters // Filters
static void FilterAddMultiHandler(void *param, calldata_t *data); // Direct callback static void FilterAddMultiHandler(void *param,
static void FilterRemoveMultiHandler(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleSourceFilterListReindexed(void *param, calldata_t *data); // Direct callback static void
void HandleSourceFilterCreated(obs_source_t *source, obs_source_t *filter); FilterRemoveMultiHandler(void *param,
void HandleSourceFilterRemoved(obs_source_t *source, obs_source_t *filter); calldata_t *data); // Direct callback
static void HandleSourceFilterNameChanged(void *param, calldata_t *data); // Direct callback static void
static void HandleSourceFilterEnableStateChanged(void *param, calldata_t *data); // Direct callback 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
// Outputs // Outputs
void HandleStreamStateChanged(ObsOutputState state); void HandleStreamStateChanged(ObsOutputState state);
@ -136,16 +177,33 @@ class EventHandler
void HandleReplayBufferSaved(); void HandleReplayBufferSaved();
// Scene Items // Scene Items
static void HandleSceneItemCreated(void *param, calldata_t *data); // Direct callback static void HandleSceneItemCreated(void *param,
static void HandleSceneItemRemoved(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback static void HandleSceneItemRemoved(void *param,
static void HandleSceneItemEnableStateChanged(void *param, calldata_t *data); // Direct callback calldata_t *data); // Direct callback
static void HandleSceneItemLockStateChanged(void *param, calldata_t *data); // Direct callback static void
static void HandleSceneItemSelected(void *param, calldata_t *data); // Direct callback HandleSceneItemListReindexed(void *param,
static void HandleSceneItemTransformChanged(void *param, calldata_t *data); // Direct callback 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
// Media Inputs // Media Inputs
static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback static void
static void HandleMediaInputPlaybackEnded(void *param, calldata_t *data); // Direct callback HandleMediaInputPlaybackStarted(void *param,
void HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action); 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

@ -63,7 +63,8 @@ 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);
@ -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);
} }
/** /**
@ -157,10 +168,12 @@ void EventHandler::HandleSourceFilterNameChanged(void *param, calldata_t *data)
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,7 +191,8 @@ 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);
@ -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);
} }
/** /**
@ -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);
} }
/** /**
@ -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);
} }
/** /**
@ -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);
} }
/** /**
@ -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);
} }
/** /**
@ -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,7 +294,8 @@ 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);
@ -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);
} }
/** /**
@ -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,7 +375,8 @@ 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);
@ -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,9 +19,12 @@ 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)
@ -44,7 +47,8 @@ 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)
@ -58,7 +62,8 @@ 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)
@ -72,7 +77,8 @@ 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)
@ -86,7 +92,8 @@ 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)
@ -100,10 +107,12 @@ 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);
@ -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,7 +140,8 @@ 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);
@ -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);
} }
/** /**
@ -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,7 +19,8 @@ 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) { switch (state) {
case OBS_WEBSOCKET_OUTPUT_STARTED: case OBS_WEBSOCKET_OUTPUT_STARTED:
case OBS_WEBSOCKET_OUTPUT_RESUMED: case OBS_WEBSOCKET_OUTPUT_RESUMED:
@ -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

@ -43,16 +43,21 @@ void EventHandler::HandleSceneItemCreated(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"] =
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);
} }
/** /**
@ -80,15 +85,19 @@ void EventHandler::HandleSceneItemRemoved(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"] =
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);
} }
/** /**
@ -114,9 +123,12 @@ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
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,7 +146,8 @@ 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);
@ -142,17 +155,20 @@ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *da
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,7 +186,8 @@ 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);
@ -178,17 +195,20 @@ void EventHandler::HandleSceneItemLockStateChanged(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;
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);
} }
/** /**
@ -213,14 +233,17 @@ void EventHandler::HandleSceneItemSelected(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);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemSelected", eventData); eventHandler->BroadcastEvent(EventSubscription::SceneItems,
"SceneItemSelected", eventData);
} }
/** /**
@ -238,7 +261,8 @@ 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);
@ -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,7 +126,8 @@ 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)
@ -130,7 +135,8 @@ void EventHandler::HandleCurrentPreviewSceneChanged()
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);
} }
/** /**
@ -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);
} }
/** /**
@ -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,7 +138,8 @@ 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);
@ -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

@ -163,7 +163,8 @@ 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.
* *

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);
} }
@ -114,7 +120,8 @@ void ConnectInfo::DrawQr(QString qrText)
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();

View File

@ -25,8 +25,7 @@ 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:

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,25 +164,32 @@ 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();
@ -183,14 +199,19 @@ void SettingsDialog::SaveFormData()
break; break;
case QMessageBox::No: case QMessageBox::No:
default: default:
ui->serverPasswordLineEdit->setText(conf->ServerPassword); ui->serverPasswordLineEdit->setText(
conf->ServerPassword);
return; 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()) ||
(ui->enableAuthenticationCheckBox->isChecked() &&
conf->ServerPassword != ui->serverPasswordLineEdit->text()) ||
(conf->BindLoopback ==
ui->allowExternalCheckBox->isChecked()) ||
(conf->ServerPort != ui->serverPortSpinBox->value()); (conf->ServerPort != ui->serverPortSpinBox->value());
conf->ServerEnabled = ui->enableWebSocketServerCheckBox->isChecked(); conf->ServerEnabled = ui->enableWebSocketServerCheckBox->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,9 +331,12 @@ 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();

View File

@ -27,8 +27,7 @@ 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:

View File

@ -32,8 +32,14 @@ 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;
@ -42,13 +48,19 @@ 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();
} }
@ -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

@ -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)
: requestHandler(requestHandler),
variables(variables), variables(variables),
haltOnFailure(haltOnFailure), haltOnFailure(haltOnFailure),
frameCount(0), frameCount(0),
sleepUntilFrame(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;
} }
@ -115,7 +130,8 @@ static void ObsTickCallback(void *param, float)
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,15 +233,22 @@ 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);
parallelResults.results.push_back(
requestResult);
lock.unlock(); lock.unlock();
parallelResults.condition.notify_one(); parallelResults.condition.notify_one();
})); }));
@ -215,7 +256,10 @@ std::vector<RequestResult> RequestBatchHandler::ProcessRequestBatch(QThreadPool
// 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,16 +193,14 @@ 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)
{ {
@ -196,16 +209,21 @@ RequestResult RequestHandler::ProcessRequest(const Request& request)
#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);

View File

@ -202,5 +202,6 @@ class RequestHandler {
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

@ -41,23 +41,30 @@ 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;
@ -83,26 +90,35 @@ 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();
} }
@ -123,8 +139,10 @@ RequestResult RequestHandler::SetPersistentData(const Request& request)
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);
} }
@ -149,18 +167,26 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
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();
@ -187,17 +213,27 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
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();
} }
@ -218,7 +254,8 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
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);
} }
@ -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();
@ -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();
} }
@ -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();
} }
@ -344,23 +396,36 @@ 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;
@ -388,24 +453,35 @@ 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,
comment) &&
request.ValidateString("parameterName", 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();
// 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);
@ -436,7 +512,9 @@ 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;
@ -471,38 +549,60 @@ RequestResult RequestHandler::GetVideoSettings(const Request&)
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.");
} }
/** /**
@ -534,7 +636,8 @@ RequestResult RequestHandler::GetStreamServiceSettings(const Request&)
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);
} }
@ -557,35 +660,53 @@ RequestResult RequestHandler::GetStreamServiceSettings(const Request&)
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);
} }
@ -610,7 +731,8 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
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

@ -37,12 +37,14 @@ 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 =
request.ValidateSource("sourceName", statusCode, comment);
if (!source) 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);
} }
@ -102,32 +107,46 @@ 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();
} }
@ -149,7 +168,8 @@ 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);
@ -176,15 +196,20 @@ 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());
@ -213,17 +238,23 @@ 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);
} }
@ -246,13 +277,16 @@ 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();
} }
@ -276,23 +310,29 @@ 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);
@ -322,8 +362,10 @@ 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.
* *
@ -53,7 +52,8 @@ 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());
@ -61,7 +61,8 @@ RequestResult RequestHandler::GetVersion(const Request&)
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);
} }
@ -93,8 +94,10 @@ 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;
@ -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();
} }
@ -154,7 +161,8 @@ 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(
RequestStatus::ResourceNotFound,
"No vendor was found by that name.");
case WebSocketApi::RequestReturnCode::NoVendorRequest: case WebSocketApi::RequestReturnCode::NoVendorRequest:
return RequestResult::Error(RequestStatus::ResourceNotFound, "No request was found by that name."); 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);
} }
@ -229,9 +247,12 @@ RequestResult RequestHandler::TriggerHotkeyByName(const Request& request)
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);
@ -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);
@ -317,19 +352,25 @@ 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

@ -40,14 +40,16 @@ RequestResult RequestHandler::GetInputList(const Request& request)
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);
} }
@ -72,14 +74,16 @@ RequestResult RequestHandler::GetInputKindList(const Request& request)
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);
} }
@ -104,7 +108,8 @@ 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) {
@ -142,43 +147,59 @@ 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);
@ -203,7 +224,8 @@ 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);
@ -232,15 +254,20 @@ 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());
@ -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);
} }
@ -303,14 +332,16 @@ 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);
} }
@ -333,23 +364,29 @@ 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
@ -382,12 +419,15 @@ 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);
@ -411,12 +451,16 @@ 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"]);
@ -441,12 +485,15 @@ 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);
@ -475,12 +522,15 @@ 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);
@ -511,32 +561,42 @@ 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);
@ -561,12 +621,15 @@ 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);
@ -591,12 +654,16 @@ 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);
@ -624,16 +691,20 @@ 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);
} }
@ -655,12 +726,17 @@ 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);
@ -692,15 +768,19 @@ 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);
} }
@ -722,15 +802,21 @@ 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);
@ -766,18 +855,22 @@ 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;
@ -803,12 +896,16 @@ 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);
} }
@ -896,20 +1004,29 @@ 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;
} }
/** /**
@ -56,15 +57,18 @@ 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;
@ -93,12 +97,16 @@ 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"];
@ -127,15 +135,20 @@ 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;
@ -162,17 +175,22 @@ 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(
RequestStatus::InvalidRequestField,
"You have specified an invalid media input action.");
case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY: case OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY:
// Shoutout to whoever implemented this API call like this // Shoutout to whoever implemented this API call like this
obs_source_media_play_pause(input, false); obs_source_media_play_pause(input, false);

View File

@ -49,7 +49,8 @@ static bool ReplayBufferAvailable()
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();
@ -71,7 +72,8 @@ RequestResult RequestHandler::GetVirtualCamStatus(const Request&)
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();
@ -98,7 +100,8 @@ RequestResult RequestHandler::ToggleVirtualCam(const Request&)
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);
@ -121,7 +124,8 @@ RequestResult RequestHandler::StartVirtualCam(const Request&)
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);
@ -146,7 +150,8 @@ RequestResult RequestHandler::StopVirtualCam(const Request&)
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();
@ -168,7 +173,8 @@ RequestResult RequestHandler::GetReplayBufferStatus(const Request&)
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();
@ -195,7 +201,8 @@ RequestResult RequestHandler::ToggleReplayBuffer(const Request&)
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);
@ -218,7 +225,8 @@ RequestResult RequestHandler::StartReplayBuffer(const Request&)
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);
@ -241,7 +249,8 @@ RequestResult RequestHandler::StopReplayBuffer(const Request&)
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);
@ -266,12 +275,14 @@ RequestResult RequestHandler::SaveReplayBuffer(const Request&)
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

@ -39,14 +39,17 @@ 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);
} }

View File

@ -39,12 +39,14 @@ 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);
} }
@ -71,12 +73,15 @@ 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);
} }
@ -103,22 +108,30 @@ 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);
@ -148,29 +161,39 @@ 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);
@ -197,7 +220,8 @@ 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);
@ -229,25 +253,33 @@ 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);
@ -291,12 +328,15 @@ 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);
} }
@ -319,8 +359,11 @@ 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);
@ -474,7 +544,9 @@ 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);
@ -504,8 +576,11 @@ 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"];
@ -536,7 +611,9 @@ 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);
@ -566,8 +643,11 @@ 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"];
@ -600,12 +680,15 @@ 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);
} }
@ -630,8 +713,11 @@ 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"];
@ -672,14 +758,17 @@ 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);
} }
@ -704,15 +793,22 @@ 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

@ -37,15 +37,19 @@ 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;
@ -92,8 +96,10 @@ RequestResult RequestHandler::GetGroupList(const Request&)
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);
} }
@ -114,7 +120,8 @@ 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);
@ -142,10 +149,12 @@ 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);
} }
@ -171,7 +180,8 @@ RequestResult RequestHandler::SetCurrentPreviewScene(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);
@ -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);
@ -230,12 +244,15 @@ 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);
@ -259,15 +276,20 @@ 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;
@ -63,7 +66,8 @@ QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t reques
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;
@ -130,12 +139,16 @@ 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);
@ -170,59 +183,79 @@ 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();
@ -257,58 +290,79 @@ 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();
} }
@ -318,14 +372,17 @@ 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);
} }
@ -335,13 +392,17 @@ 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

@ -41,16 +41,22 @@ 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);
} }

View File

@ -38,7 +38,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
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);
} }
@ -62,14 +63,17 @@ RequestResult RequestHandler::GetSceneTransitionList(const Request&)
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);
} }
@ -95,7 +99,9 @@ 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;
@ -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);
@ -246,7 +271,9 @@ 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);
@ -269,7 +296,8 @@ 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);
@ -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

@ -39,7 +39,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
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);
} }
@ -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(
OBS_TASK_UI,
[](void *param) {
auto studioModeEnabled = (bool *)param; auto studioModeEnabled = (bool *)param;
obs_frontend_set_preview_program_mode(*studioModeEnabled); obs_frontend_set_preview_program_mode(
}, &studioModeEnabled, true); *studioModeEnabled);
},
&studioModeEnabled, true);
} }
return RequestResult::Success(); return RequestResult::Success();
@ -92,7 +99,8 @@ 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);
@ -117,7 +125,8 @@ 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);
@ -142,12 +151,15 @@ 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);
@ -171,8 +183,8 @@ 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;

View File

@ -29,8 +29,10 @@ 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
executionType)
: RequestType(requestType),
HasRequestData(requestData.is_object()), HasRequestData(requestData.is_object()),
RequestData(GetDefaultJsonObject(requestData)), RequestData(GetDefaultJsonObject(requestData)),
ExecutionType(executionType) ExecutionType(executionType)
@ -39,78 +41,103 @@ Request::Request(const std::string &requestType, const json &requestData, const
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,8 +18,11 @@ 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,
RequestBatchExecutionType::RequestBatchExecutionType executionType,
const json &inputVariables, const json &outputVariables)
: Request(requestType, requestData, executionType),
InputVariables(inputVariables), InputVariables(inputVariables),
OutputVariables(outputVariables) 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,8 +19,9 @@ 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)
: StatusCode(statusCode),
ResponseData(responseData), ResponseData(responseData),
Comment(comment), Comment(comment),
SleepFrames(0) 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

@ -263,7 +263,6 @@ namespace RequestStatus {
*/ */
StudioModeNotActive = 506, StudioModeNotActive = 506,
/** /**
* The resource was not found. * The resource was not found.
* *

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

@ -27,6 +27,7 @@ namespace Utils {
// 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: public:
StdFunctionRunnable(std::function<void()> func); StdFunctionRunnable(std::function<void()> func);
void run() override; void run() override;

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

@ -26,7 +26,8 @@ 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 authenticationString);
std::string GeneratePassword(size_t length = 16); std::string GeneratePassword(size_t length = 16);
} }
} }

View File

@ -67,11 +67,13 @@ void obs_data_set_json_object_item(obs_data_t *d, json j)
} 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);
} }
@ -168,8 +173,7 @@ json Utils::Json::ObsDataToJson(obs_data_t *d, bool includeDefault)
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:;
;
} }
} }
@ -185,14 +189,18 @@ 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

@ -31,7 +31,11 @@ namespace Utils {
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

@ -27,7 +27,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
// 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 *) {}
@ -44,22 +46,42 @@ 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);
@ -189,7 +211,8 @@ namespace Utils {
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,
bool includeDisabled = false);
std::vector<json> GetListPropertyItems(obs_property_t *property); std::vector<json> GetListPropertyItems(obs_property_t *property);
std::vector<std::string> GetTransitionKindList(); std::vector<std::string> GetTransitionKindList();
std::vector<json> GetSceneTransitionList(); std::vector<json> GetSceneTransitionList();
@ -204,15 +227,31 @@ namespace Utils {
namespace SearchHelper { namespace SearchHelper {
obs_hotkey_t *GetHotkeyByName(std::string name); obs_hotkey_t *GetHotkeyByName(std::string name);
obs_source_t *GetSceneTransitionByName(std::string name); // Increments source ref. Use OBSSourceAutoRelease obs_source_t *GetSceneTransitionByName(
obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name, int offset = 0); // Increments ref. Use OBSSceneItemAutoRelease std::string name); // Increments source ref. Use OBSSourceAutoRelease
obs_sceneitem_t *GetSceneItemByName(
obs_scene_t *scene, std::string name,
int offset = 0); // Increments ref. Use OBSSceneItemAutoRelease
} }
namespace ActionHelper { 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 *
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 CreateSceneItem(obs_source_t *source, obs_scene_t *scene,
obs_source_t *CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings); // Increments source ref. Use OBSSourceAutoRelease bool sceneItemEnabled = true,
void SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter, size_t index); 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

@ -33,7 +33,8 @@ void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
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,10 +111,14 @@ 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);

View File

@ -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;
} }
@ -127,28 +130,39 @@ 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"] =
StringHelper::GetSourceType(itemSource);
if (obs_source_get_type(itemSource) ==
OBS_SOURCE_TYPE_INPUT)
item["inputKind"] =
obs_source_get_id(itemSource);
else else
item["inputKind"] = nullptr; item["inputKind"] = nullptr;
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE) if (obs_source_get_type(itemSource) ==
item["isGroup"] = obs_source_is_group(itemSource); OBS_SOURCE_TYPE_SCENE)
item["isGroup"] =
obs_source_is_group(itemSource);
else else
item["isGroup"] = nullptr; item["isGroup"] = nullptr;
} }
@ -156,7 +170,8 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene,
enumData->first.push_back(item); enumData->first.push_back(item);
return true; return true;
}, &enumData); },
&enumData);
return enumData.first; return enumData.first;
} }
@ -180,13 +195,15 @@ std::vector<json> Utils::Obs::ArrayHelper::GetInputList(std::string inputKind)
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);
} }
@ -293,11 +321,13 @@ std::vector<std::string> Utils::Obs::ArrayHelper::GetFilterKindList()
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,
void *param) {
auto filters = reinterpret_cast<std::vector<json> *>(param); auto filters = reinterpret_cast<std::vector<json> *>(param);
json filterJson; json filterJson;
@ -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

@ -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,7 +62,8 @@ 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,
void *priv_data) {
auto filterSearch = static_cast<FilterSearch *>(priv_data); auto filterSearch = static_cast<FilterSearch *>(priv_data);
if (filter == filterSearch->filter) if (filter == filterSearch->filter)

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);
std::string sourceName =
obs_source_get_name(itemSource);
if (sourceName == enumData->name) { if (sourceName == enumData->name) {
if (enumData->offset > 0) { if (enumData->offset > 0) {
enumData->offset--; enumData->offset--;
} else { } else {
if (enumData->ret) // Release existing selection in the case of last match selection if (enumData->ret) // Release existing selection in the case of last match selection
obs_sceneitem_release(enumData->ret); obs_sceneitem_release(
enumData->ret);
obs_sceneitem_addref(sceneItem); obs_sceneitem_addref(sceneItem);
enumData->ret = sceneItem; enumData->ret = sceneItem;
if (enumData->offset == 0) // Only break if in normal selection mode (not offset == -1) if (enumData->offset ==
0) // Only break if in normal selection mode (not offset == -1)
return false; 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()
{ {
@ -83,7 +85,8 @@ 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:
@ -135,7 +138,8 @@ 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:
@ -149,7 +153,8 @@ 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:
@ -173,7 +178,10 @@ 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();
} }

View File

@ -26,8 +26,8 @@ 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),
@ -36,25 +36,33 @@ Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *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++) {
@ -139,16 +151,19 @@ 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,
sampleCount);
break; break;
case TRUE_PEAK_METER: case TRUE_PEAK_METER:
peak = GetTruePeak(previousSamples, samples, sampleCount); peak = GetTruePeak(previousSamples, samples,
sampleCount);
break; break;
} }
@ -156,28 +171,44 @@ void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
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];
_previousSamples[channelNumber][2] =
_previousSamples[channelNumber][3];
_previousSamples[channelNumber][3] =
samples[sampleCount - 1];
break; break;
case 2: case 2:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][0] =
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][2];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][1] =
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] =
samples[sampleCount - 2];
_previousSamples[channelNumber][3] =
samples[sampleCount - 1];
break; break;
case 3: case 3:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][0] =
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][1] =
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; samples[sampleCount - 3];
_previousSamples[channelNumber][2] =
samples[sampleCount - 2];
_previousSamples[channelNumber][3] =
samples[sampleCount - 1];
break; break;
default: default:
_previousSamples[channelNumber][0] = samples[sampleCount - 4]; _previousSamples[channelNumber][0] =
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; samples[sampleCount - 4];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][1] =
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; samples[sampleCount - 3];
_previousSamples[channelNumber][2] =
samples[sampleCount - 2];
_previousSamples[channelNumber][3] =
samples[sampleCount - 1];
} }
_peak[channelNumber] = peak; _peak[channelNumber] = peak;
@ -190,7 +221,8 @@ 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;
@ -212,7 +244,9 @@ 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);
@ -226,17 +260,17 @@ 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)
@ -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,10 +351,12 @@ 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);
@ -330,7 +375,8 @@ 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);
@ -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

@ -65,7 +65,10 @@ namespace Utils {
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,
obs_source_t *source,
const struct audio_data *data,
bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd); static void InputVolumeCallback(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,22 +53,34 @@ 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(),
[=](std::pair<QString, uint8_t> a,
std::pair<QString, uint8_t> b) {
return a.second < b.second; return a.second < b.second;
}); });
@ -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(
OBS_TASK_UI,
[](void *param) {
void *systemTrayPtr = obs_frontend_get_system_tray(); void *systemTrayPtr = obs_frontend_get_system_tray();
auto systemTray = static_cast<QSystemTrayIcon*>(systemTrayPtr); 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);
systemTray->showMessage(notification->title,
notification->body,
notification->icon);
delete notification; delete notification;
}, (void*)notification, false); },
(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

@ -29,8 +29,10 @@ namespace Utils {
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,
QString body);
bool GetTextFileContent(std::string fileName, std::string &content); bool GetTextFileContent(std::string fileName, std::string &content);
bool SetTextFileContent(std::string filePath, std::string content, bool createNew = true); 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()
@ -90,11 +77,16 @@ void WebSocketServer::ServerRunner()
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,
"[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s",
e.what());
} catch (const std::exception &e) { } catch (const std::exception &e) {
blog(LOG_ERROR, "[WebSocketServer::ServerRunner] websocketpp instance returned an error: %s", e.what()); 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,13 +165,17 @@ 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;
} }
@ -172,13 +186,16 @@ void WebSocketServer::Stop()
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,23 +215,28 @@ 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;
@ -226,7 +248,9 @@ std::vector<WebSocketServer::WebSocketSessionState> WebSocketServer::GetWebSocke
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();
@ -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,15 +34,11 @@ 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;
@ -59,25 +55,26 @@ class WebSocketServer : QObject
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 =
WebSocketCloseCode::DontClose;
std::string closeReason; std::string closeReason;
json result; json result;
}; };
@ -88,10 +85,17 @@ class WebSocketServer : QObject
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;
@ -102,5 +106,7 @@ class WebSocketServer : QObject
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,33 +97,48 @@ 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(
session->OperationMutex);
if (session->IsIdentified()) { if (session->IsIdentified()) {
ret.closeCode = WebSocketCloseCode::AlreadyIdentified; ret.closeCode = WebSocketCloseCode::AlreadyIdentified;
ret.closeReason = "You are already Identified with the obs-websocket server."; ret.closeReason =
"You are already Identified with the obs-websocket server.";
return; return;
} }
if (session->AuthenticationRequired()) { if (session->AuthenticationRequired()) {
if (!payloadData.contains("authentication")) { if (!payloadData.contains("authentication")) {
ret.closeCode = WebSocketCloseCode::AuthenticationFailed; ret.closeCode =
ret.closeReason = "Your payload's data is missing an `authentication` string, however authentication is required."; WebSocketCloseCode::AuthenticationFailed;
ret.closeReason =
"Your payload's data is missing an `authentication` string, however authentication is required.";
return; return;
} }
if (!Utils::Crypto::CheckAuthenticationString(session->Secret(), session->Challenge(), payloadData["authentication"])) { if (!Utils::Crypto::CheckAuthenticationString(
session->Secret(), session->Challenge(),
payloadData["authentication"])) {
auto conf = GetConfig(); auto conf = GetConfig();
if (conf && conf->AlertsEnabled) { if (conf && conf->AlertsEnabled) {
QString title = obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Title"); QString title = obs_module_text(
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.AuthenticationFailed.Body")).arg(QString::fromStdString(session->RemoteAddress())); "OBSWebSocket.TrayNotification.AuthenticationFailed.Title");
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Warning, title, body); 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.closeCode =
WebSocketCloseCode::AuthenticationFailed;
ret.closeReason = "Authentication failed."; ret.closeReason = "Authentication failed.";
return; return;
} }
@ -120,19 +146,24 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
if (!payloadData.contains("rpcVersion")) { if (!payloadData.contains("rpcVersion")) {
ret.closeCode = WebSocketCloseCode::MissingDataField; ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload's data is missing an `rpcVersion`."; ret.closeReason =
"Your payload's data is missing an `rpcVersion`.";
return; return;
} }
if (!payloadData["rpcVersion"].is_number_unsigned()) { if (!payloadData["rpcVersion"].is_number_unsigned()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType; ret.closeCode =
ret.closeReason = "Your `rpcVersion` is not an unsigned number."; WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `rpcVersion` is not an unsigned number.";
} }
uint8_t requestedRpcVersion = payloadData["rpcVersion"]; uint8_t requestedRpcVersion = payloadData["rpcVersion"];
if (!IsSupportedRpcVersion(requestedRpcVersion)) { if (!IsSupportedRpcVersion(requestedRpcVersion)) {
ret.closeCode = WebSocketCloseCode::UnsupportedRpcVersion; ret.closeCode =
ret.closeReason = "Your requested RPC version is not supported by this server."; WebSocketCloseCode::UnsupportedRpcVersion;
ret.closeReason =
"Your requested RPC version is not supported by this server.";
return; return;
} }
session->SetRpcVersion(requestedRpcVersion); session->SetRpcVersion(requestedRpcVersion);
@ -144,7 +175,8 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
// Increment refs for event subscriptions // Increment refs for event subscriptions
auto eventHandler = GetEventHandler(); auto eventHandler = GetEventHandler();
eventHandler->ProcessSubscription(session->EventSubscriptions()); eventHandler->ProcessSubscription(
session->EventSubscriptions());
// Mark session as identified // Mark session as identified
session->SetIsIdentified(true); session->SetIsIdentified(true);
@ -152,20 +184,29 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
// Send desktop notification. TODO: Move to UI code // Send desktop notification. TODO: Move to UI code
auto conf = GetConfig(); auto conf = GetConfig();
if (conf && conf->AlertsEnabled) { if (conf && conf->AlertsEnabled) {
QString title = obs_module_text("OBSWebSocket.TrayNotification.Identified.Title"); QString title = obs_module_text(
QString body = QString(obs_module_text("OBSWebSocket.TrayNotification.Identified.Body")).arg(QString::fromStdString(session->RemoteAddress())); "OBSWebSocket.TrayNotification.Identified.Title");
Utils::Platform::SendTrayNotification(QSystemTrayIcon::Information, title, body); 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["op"] = WebSocketOpCode::Identified;
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion(); ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
} return; }
return;
case WebSocketOpCode::Reidentify: { // Reidentify case WebSocketOpCode::Reidentify: { // Reidentify
std::unique_lock<std::mutex> sessionLock(session->OperationMutex); std::unique_lock<std::mutex> sessionLock(
session->OperationMutex);
// Decrement refs for current subscriptions // Decrement refs for current subscriptions
auto eventHandler = GetEventHandler(); auto eventHandler = GetEventHandler();
eventHandler->ProcessUnsubscription(session->EventSubscriptions()); eventHandler->ProcessUnsubscription(
session->EventSubscriptions());
SetSessionParameters(session, ret, payloadData); SetSessionParameters(session, ret, payloadData);
if (ret.closeCode != WebSocketCloseCode::DontClose) { if (ret.closeCode != WebSocketCloseCode::DontClose) {
@ -173,102 +214,139 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
} }
// Increment refs for new subscriptions // Increment refs for new subscriptions
eventHandler->ProcessSubscription(session->EventSubscriptions()); eventHandler->ProcessSubscription(
session->EventSubscriptions());
ret.result["op"] = WebSocketOpCode::Identified; ret.result["op"] = WebSocketOpCode::Identified;
ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion(); ret.result["d"]["negotiatedRpcVersion"] = session->RpcVersion();
} return; }
return;
case WebSocketOpCode::Request: { // Request case WebSocketOpCode::Request: { // Request
// RequestID checking has to be done here where we are able to close the connection. // RequestID checking has to be done here where we are able to close the connection.
if (!payloadData.contains("requestId")) { if (!payloadData.contains("requestId")) {
ret.closeCode = WebSocketCloseCode::MissingDataField; ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload data is missing a `requestId`."; ret.closeReason =
"Your payload data is missing a `requestId`.";
return; return;
} }
RequestHandler requestHandler(session); RequestHandler requestHandler(session);
Request request(payloadData["requestType"], payloadData["requestData"]); Request request(payloadData["requestType"],
payloadData["requestData"]);
RequestResult requestResult = requestHandler.ProcessRequest(request); RequestResult requestResult =
requestHandler.ProcessRequest(request);
json resultPayloadData; json resultPayloadData;
resultPayloadData["requestType"] = payloadData["requestType"]; resultPayloadData["requestType"] = payloadData["requestType"];
resultPayloadData["requestId"] = payloadData["requestId"]; resultPayloadData["requestId"] = payloadData["requestId"];
resultPayloadData["requestStatus"] = { resultPayloadData["requestStatus"] = {
{"result", requestResult.StatusCode == RequestStatus::Success}, {"result",
{"code", requestResult.StatusCode} requestResult.StatusCode == RequestStatus::Success},
}; {"code", requestResult.StatusCode}};
if (!requestResult.Comment.empty()) if (!requestResult.Comment.empty())
resultPayloadData["requestStatus"]["comment"] = requestResult.Comment; resultPayloadData["requestStatus"]["comment"] =
requestResult.Comment;
if (requestResult.ResponseData.is_object()) if (requestResult.ResponseData.is_object())
resultPayloadData["responseData"] = requestResult.ResponseData; resultPayloadData["responseData"] =
requestResult.ResponseData;
ret.result["op"] = WebSocketOpCode::RequestResponse; ret.result["op"] = WebSocketOpCode::RequestResponse;
ret.result["d"] = resultPayloadData; ret.result["d"] = resultPayloadData;
} return; }
return;
case WebSocketOpCode::RequestBatch: { // RequestBatch case WebSocketOpCode::RequestBatch: { // RequestBatch
// RequestID checking has to be done here where we are able to close the connection. // RequestID checking has to be done here where we are able to close the connection.
if (!payloadData.contains("requestId")) { if (!payloadData.contains("requestId")) {
ret.closeCode = WebSocketCloseCode::MissingDataField; ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload data is missing a `requestId`."; ret.closeReason =
"Your payload data is missing a `requestId`.";
return; return;
} }
if (!payloadData.contains("requests")) { if (!payloadData.contains("requests")) {
ret.closeCode = WebSocketCloseCode::MissingDataField; ret.closeCode = WebSocketCloseCode::MissingDataField;
ret.closeReason = "Your payload data is missing a `requests`."; ret.closeReason =
"Your payload data is missing a `requests`.";
return; return;
} }
if (!payloadData["requests"].is_array()) { if (!payloadData["requests"].is_array()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType; ret.closeCode =
WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason = "Your `requests` is not an array."; ret.closeReason = "Your `requests` is not an array.";
return; return;
} }
RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::SerialRealtime; RequestBatchExecutionType::RequestBatchExecutionType
if (payloadData.contains("executionType") && !payloadData["executionType"].is_null()) { executionType =
RequestBatchExecutionType::SerialRealtime;
if (payloadData.contains("executionType") &&
!payloadData["executionType"].is_null()) {
if (!payloadData["executionType"].is_number_unsigned()) { if (!payloadData["executionType"].is_number_unsigned()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType; ret.closeCode =
ret.closeReason = "Your `executionType` is not a number."; WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `executionType` is not a number.";
return; return;
} }
int8_t requestedExecutionType = payloadData["executionType"]; int8_t requestedExecutionType =
if (!RequestBatchExecutionType::IsValid(requestedExecutionType) || requestedExecutionType == RequestBatchExecutionType::None) { payloadData["executionType"];
ret.closeCode = WebSocketCloseCode::InvalidDataFieldValue; if (!RequestBatchExecutionType::IsValid(
ret.closeReason = "Your `executionType` has an invalid value."; requestedExecutionType) ||
requestedExecutionType ==
RequestBatchExecutionType::None) {
ret.closeCode =
WebSocketCloseCode::InvalidDataFieldValue;
ret.closeReason =
"Your `executionType` has an invalid value.";
return; return;
} }
// The thread pool must support 2 or more threads else parallel requests will deadlock. // The thread pool must support 2 or more threads else parallel requests will deadlock.
if (requestedExecutionType == RequestBatchExecutionType::Parallel && _threadPool.maxThreadCount() < 2) { if (requestedExecutionType ==
ret.closeCode = WebSocketCloseCode::UnsupportedFeature; RequestBatchExecutionType::Parallel &&
ret.closeReason = "Parallel request batch processing is not available on this system due to limited core count."; _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; return;
} }
executionType = (RequestBatchExecutionType::RequestBatchExecutionType)requestedExecutionType; executionType = (RequestBatchExecutionType::
RequestBatchExecutionType)
requestedExecutionType;
} }
if (payloadData.contains("variables") && !payloadData["variables"].is_null()) { if (payloadData.contains("variables") &&
!payloadData["variables"].is_null()) {
if (!payloadData.is_object()) { if (!payloadData.is_object()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType; ret.closeCode =
ret.closeReason = "Your `variables` is not an object."; WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `variables` is not an object.";
return; return;
} }
if (executionType == RequestBatchExecutionType::Parallel) { if (executionType ==
ret.closeCode = WebSocketCloseCode::UnsupportedFeature; RequestBatchExecutionType::Parallel) {
ret.closeReason = "Variables are not supported in Parallel mode."; ret.closeCode =
WebSocketCloseCode::UnsupportedFeature;
ret.closeReason =
"Variables are not supported in Parallel mode.";
return; return;
} }
} }
bool haltOnFailure = false; bool haltOnFailure = false;
if (payloadData.contains("haltOnFailure") && !payloadData["haltOnFailure"].is_null()) { if (payloadData.contains("haltOnFailure") &&
!payloadData["haltOnFailure"].is_null()) {
if (!payloadData["haltOnFailure"].is_boolean()) { if (!payloadData["haltOnFailure"].is_boolean()) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldType; ret.closeCode =
ret.closeReason = "Your `haltOnFailure` is not a boolean."; WebSocketCloseCode::InvalidDataFieldType;
ret.closeReason =
"Your `haltOnFailure` is not a boolean.";
return; return;
} }
@ -279,30 +357,41 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
std::vector<RequestBatchRequest> requestsVector; std::vector<RequestBatchRequest> requestsVector;
for (auto &requestJson : requests) for (auto &requestJson : requests)
requestsVector.emplace_back(requestJson["requestType"], requestJson["requestData"], executionType, requestJson["inputVariables"], requestJson["outputVariables"]); requestsVector.emplace_back(
requestJson["requestType"],
requestJson["requestData"], executionType,
requestJson["inputVariables"],
requestJson["outputVariables"]);
auto resultsVector = RequestBatchHandler::ProcessRequestBatch(_threadPool, session, executionType, requestsVector, payloadData["variables"], haltOnFailure); auto resultsVector = RequestBatchHandler::ProcessRequestBatch(
_threadPool, session, executionType, requestsVector,
payloadData["variables"], haltOnFailure);
size_t i = 0; size_t i = 0;
std::vector<json> results; std::vector<json> results;
for (auto &requestResult : resultsVector) { for (auto &requestResult : resultsVector) {
results.push_back(ConstructRequestResult(requestResult, requests[i])); results.push_back(ConstructRequestResult(requestResult,
requests[i]));
i++; i++;
} }
ret.result["op"] = WebSocketOpCode::RequestBatchResponse; ret.result["op"] = WebSocketOpCode::RequestBatchResponse;
ret.result["d"]["requestId"] = payloadData["requestId"]; ret.result["d"]["requestId"] = payloadData["requestId"];
ret.result["d"]["results"] = results; ret.result["d"]["results"] = results;
} return; }
return;
default: default:
ret.closeCode = WebSocketCloseCode::UnknownOpCode; ret.closeCode = WebSocketCloseCode::UnknownOpCode;
ret.closeReason = std::string("Unknown OpCode: ") + std::to_string(opCode); ret.closeReason = std::string("Unknown OpCode: ") +
std::to_string(opCode);
return; 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;
@ -326,34 +415,57 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, const std::string
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); _server.send(
(websocketpp::connection_hdl)
it.first,
messageJson,
websocketpp::frame::opcode::text,
errorCode);
it.second->IncrementOutgoingMessages(); it.second->IncrementOutgoingMessages();
break; break;
case WebSocketEncoding::MsgPack: case WebSocketEncoding::MsgPack:
if (messageMsgPack.empty()) { if (messageMsgPack.empty()) {
auto msgPackData = json::to_msgpack(eventMessage); auto msgPackData =
messageMsgPack = std::string(msgPackData.begin(), msgPackData.end()); json::to_msgpack(
eventMessage);
messageMsgPack = std::string(
msgPackData.begin(),
msgPackData.end());
} }
_server.send((websocketpp::connection_hdl)it.first, messageMsgPack, websocketpp::frame::opcode::binary, errorCode); _server.send(
(websocketpp::connection_hdl)
it.first,
messageMsgPack,
websocketpp::frame::opcode::binary,
errorCode);
it.second->IncrementOutgoingMessages(); it.second->IncrementOutgoingMessages();
break; 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,8 +20,8 @@ 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),

View File

@ -29,8 +29,7 @@ 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();