From 7e5716185e3c86ff83001c6cad41497a5de39bf4 Mon Sep 17 00:00:00 2001 From: Palakis Date: Tue, 14 Nov 2017 09:14:33 +0100 Subject: [PATCH] code: reorder and refactor WSRequestHandlers --- CMakeLists.txt | 1 + src/Utils.cpp | 24 +- src/Utils.h | 5 +- src/WSRequestHandler_General.cpp | 3 +- src/WSRequestHandler_Profiles.cpp | 7 +- src/WSRequestHandler_ReplayBuffer.cpp | 2 +- src/WSRequestHandler_SceneCollections.cpp | 18 +- src/WSRequestHandler_SceneItems.cpp | 568 +++++++++++ src/WSRequestHandler_Sources.cpp | 1107 ++++++--------------- 9 files changed, 883 insertions(+), 852 deletions(-) create mode 100644 src/WSRequestHandler_SceneItems.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 18494960..281331a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ set(obs-websocket_SOURCES src/WSRequestHandler_ReplayBuffer.cpp src/WSRequestHandler_SceneCollections.cpp src/WSRequestHandler_Scenes.cpp + src/WSRequestHandler_SceneItems.cpp src/WSRequestHandler_Sources.cpp src/WSRequestHandler_Streaming.cpp src/WSRequestHandler_StudioMode.cpp diff --git a/src/Utils.cpp b/src/Utils.cpp index dcf924dc..9959e8fc 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -35,7 +35,7 @@ const char* qstring_data_copy(QString value) { return constStringData; } -obs_data_array_t* stringListToArray(char** strings, char* key) { +obs_data_array_t* Utils::StringListToArray(char** strings, char* key) { if (!strings) return obs_data_array_create(); @@ -222,22 +222,6 @@ obs_data_t* Utils::GetSceneData(obs_source_t* source) { return sceneData; } -obs_data_array_t* Utils::GetSceneCollections() { - char** sceneCollections = obs_frontend_get_scene_collections(); - obs_data_array_t* list = stringListToArray(sceneCollections, "sc-name"); - - bfree(sceneCollections); - return list; -} - -obs_data_array_t* Utils::GetProfiles() { - char** profiles = obs_frontend_get_profiles(); - obs_data_array_t* list = stringListToArray(profiles, "profile-name"); - - bfree(profiles); - return list; -} - QSpinBox* Utils::GetTransitionDurationControl() { QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window(); return window->findChild("transitionDuration"); @@ -315,7 +299,7 @@ void Utils::TransitionToProgram() { transitionBtn->click(); } -const char* Utils::OBSVersionString() { +QString Utils::OBSVersionString() { uint32_t version = obs_get_version(); uint8_t major, minor, patch; @@ -323,8 +307,8 @@ const char* Utils::OBSVersionString() { minor = (version >> 16) & 0xFF; patch = version & 0xFF; - char* result = (char*)bmalloc(sizeof(char) * 12); - sprintf(result, "%d.%d.%d", major, minor, patch); + QString result = QString("%1.%2.%3") + .arg(major).arg(minor).arg(patch); return result; } diff --git a/src/Utils.h b/src/Utils.h index 3eb2759c..aac52246 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -36,6 +36,7 @@ const char* qstring_data_copy(QString value); class Utils { public: + static obs_data_array_t* StringListToArray(char** strings, char* key); static obs_data_array_t* GetSceneItems(obs_source_t* source); static obs_data_t* GetSceneItemData(obs_sceneitem_t* item); static obs_sceneitem_t* GetSceneItemFromName( @@ -48,7 +49,6 @@ class Utils { static obs_data_array_t* GetScenes(); static obs_data_t* GetSceneData(obs_source_t* source); - static obs_data_array_t* GetSceneCollections(); static obs_data_array_t* GetProfiles(); static QSpinBox* GetTransitionDurationControl(); @@ -64,7 +64,7 @@ class Utils { static void TransitionToProgram(); - static const char* OBSVersionString(); + static QString OBSVersionString(); static QSystemTrayIcon* GetTrayIcon(); static void SysTrayNotify( @@ -73,6 +73,7 @@ class Utils { QString title = QString("obs-websocket")); static QString FormatIPAddress(QHostAddress &addr); + static const char* GetRecordingFolder(); static bool SetRecordingFolder(const char* path); diff --git a/src/WSRequestHandler_General.cpp b/src/WSRequestHandler_General.cpp index 797c1c6d..afcb9053 100644 --- a/src/WSRequestHandler_General.cpp +++ b/src/WSRequestHandler_General.cpp @@ -12,7 +12,7 @@ * @return {double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility. * @return {String} `obs-websocket-version` obs-websocket plugin version. * @return {String} `obs-studio-version` OBS Studio program version. - * @return {String|Array} `available-requests` List of available request types. + * @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). * * @api requests * @name GetVersion @@ -109,6 +109,7 @@ void WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) { * @api requests * @name SetHeartbeat * @category general + * @since unreleased */ void WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) { if (!req->hasField("enable")) { diff --git a/src/WSRequestHandler_Profiles.cpp b/src/WSRequestHandler_Profiles.cpp index ab25ff4d..b9f2b5d6 100644 --- a/src/WSRequestHandler_Profiles.cpp +++ b/src/WSRequestHandler_Profiles.cpp @@ -58,10 +58,13 @@ void WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) { * @since 4.0.0 */ void WSRequestHandler::HandleListProfiles(WSRequestHandler* req) { - OBSDataArrayAutoRelease profiles = Utils::GetProfiles(); + char** profiles = obs_frontend_get_profiles(); + OBSDataArrayAutoRelease list = + Utils::StringListToArray(profiles, "profile-name"); + bfree(profiles); OBSDataAutoRelease response = obs_data_create(); - obs_data_set_array(response, "profiles", profiles); + obs_data_set_array(response, "profiles", list); req->SendOKResponse(response); } diff --git a/src/WSRequestHandler_ReplayBuffer.cpp b/src/WSRequestHandler_ReplayBuffer.cpp index d67f24c1..0250ef02 100644 --- a/src/WSRequestHandler_ReplayBuffer.cpp +++ b/src/WSRequestHandler_ReplayBuffer.cpp @@ -66,7 +66,7 @@ void WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) { } /** -* Save and flush the contents of the Replay Buffer to disk. This is +* Flush and save the contents of the Replay Buffer to disk. This is * basically the same as triggering the "Save Replay Buffer" hotkey. * Will return an `error` if the Replay Buffer is not active. * diff --git a/src/WSRequestHandler_SceneCollections.cpp b/src/WSRequestHandler_SceneCollections.cpp index 83a8bd13..69456b7b 100644 --- a/src/WSRequestHandler_SceneCollections.cpp +++ b/src/WSRequestHandler_SceneCollections.cpp @@ -47,11 +47,25 @@ void WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) { req->SendOKResponse(response); } +/** + * List available scene collections + * + * @return {Object|Array} `scene-collections` Scene collections list + * @return {String} `scene-collections.*.` + * + * @api requests + * @name ListSceneCollections + * @category scene collections + * @since 4.0.0 + */ void WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) { - OBSDataArrayAutoRelease sceneCollections = Utils::GetSceneCollections(); + char** sceneCollections = obs_frontend_get_scene_collections(); + OBSDataArrayAutoRelease list = + Utils::StringListToArray(sceneCollections, "sc-name"); + bfree(sceneCollections); OBSDataAutoRelease response = obs_data_create(); - obs_data_set_array(response, "scene-collections", sceneCollections); + obs_data_set_array(response, "scene-collections", list); req->SendOKResponse(response); } diff --git a/src/WSRequestHandler_SceneItems.cpp b/src/WSRequestHandler_SceneItems.cpp new file mode 100644 index 00000000..0b78f890 --- /dev/null +++ b/src/WSRequestHandler_SceneItems.cpp @@ -0,0 +1,568 @@ +#include +#include "Utils.h" + +#include "WSRequestHandler.h" + +/** +* Gets the scene specific properties of the specified source item. +* +* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. +* @param {String} `item` The name of the source. +* +* @return {String} `name` The name of the source. +* @return {int} `position.x` The x position of the source from the left. +* @return {int} `position.y` The y position of the source from the top. +* @return {int} `position.alignment` The point on the source that the item is manipulated from. +* @return {double} `rotation` The clockwise rotation of the item in degrees around the point of alignment. +* @return {double} `scale.x` The x-scale factor of the source. +* @return {double} `scale.y` The y-scale factor of the source. +* @return {int} `crop.top` The number of pixels cropped off the top of the source before scaling. +* @return {int} `crop.right` The number of pixels cropped off the right of the source before scaling. +* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling. +* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling. +* @return {bool} `visible` If the source is visible. +* @return {String} `bounds.type` Type of bounding box. +* @return {int} `bounds.alignment` Alignment of the bounding box. +* @return {double} `bounds.x` Width of the bounding box. +* @return {double} `bounds.y` Height of the bounding box. +* +* @api requests +* @name GetSceneItemProperties +* @category scene items +* @since unreleased +*/ +void WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) { + if (!req->hasField("item")) { + req->SendErrorResponse("missing request parameters"); + return; + } + + QString itemName = obs_data_get_string(req->data, "item"); + if (itemName.isEmpty()) { + req->SendErrorResponse("invalid request parameters"); + return; + } + + QString sceneName = obs_data_get_string(req->data, "scene-name"); + OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); + if (!scene) { + req->SendErrorResponse("requested scene doesn't exist"); + return; + } + + OBSSceneItemAutoRelease sceneItem = + Utils::GetSceneItemFromName(scene, itemName); + if (!sceneItem) { + req->SendErrorResponse("specified scene item doesn't exist"); + return; + } + + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_string(data, "name", itemName.toUtf8()); + + OBSDataAutoRelease posData = obs_data_create(); + vec2 pos; + obs_sceneitem_get_pos(sceneItem, &pos); + obs_data_set_double(posData, "x", pos.x); + obs_data_set_double(posData, "y", pos.y); + obs_data_set_int(posData, "alignment", obs_sceneitem_get_alignment(sceneItem)); + obs_data_set_obj(data, "position", posData); + + obs_data_set_double(data, "rotation", obs_sceneitem_get_rot(sceneItem)); + + OBSDataAutoRelease scaleData = obs_data_create(); + vec2 scale; + obs_sceneitem_get_scale(sceneItem, &scale); + obs_data_set_double(scaleData, "x", scale.x); + obs_data_set_double(scaleData, "y", scale.y); + obs_data_set_obj(data, "scale", scaleData); + + OBSDataAutoRelease cropData = obs_data_create(); + obs_sceneitem_crop crop; + obs_sceneitem_get_crop(sceneItem, &crop); + obs_data_set_int(cropData, "left", crop.left); + obs_data_set_int(cropData, "top", crop.top); + obs_data_set_int(cropData, "right", crop.right); + obs_data_set_int(cropData, "bottom", crop.bottom); + obs_data_set_obj(data, "crop", cropData); + + obs_data_set_bool(data, "visible", obs_sceneitem_visible(sceneItem)); + + OBSDataAutoRelease boundsData = obs_data_create(); + obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem); + if (boundsType == OBS_BOUNDS_NONE) { + obs_data_set_string(boundsData, "type", "OBS_BOUNDS_NONE"); + } + else { + switch (boundsType) { + case OBS_BOUNDS_STRETCH: { + obs_data_set_string(boundsData, "type", "OBS_BOUNDS_STRETCH"); + break; + } + case OBS_BOUNDS_SCALE_INNER: { + obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_INNER"); + break; + } + case OBS_BOUNDS_SCALE_OUTER: { + obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_OUTER"); + break; + } + case OBS_BOUNDS_SCALE_TO_WIDTH: { + obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_WIDTH"); + break; + } + case OBS_BOUNDS_SCALE_TO_HEIGHT: { + obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_HEIGHT"); + break; + } + case OBS_BOUNDS_MAX_ONLY: { + obs_data_set_string(boundsData, "type", "OBS_BOUNDS_MAX_ONLY"); + break; + } + } + obs_data_set_int(boundsData, "alignment", obs_sceneitem_get_bounds_alignment(sceneItem)); + vec2 bounds; + obs_sceneitem_get_bounds(sceneItem, &bounds); + obs_data_set_double(boundsData, "x", bounds.x); + obs_data_set_double(boundsData, "y", bounds.y); + } + obs_data_set_obj(data, "bounds", boundsData); + + req->SendOKResponse(data); +} + +/** +* Sets the scene specific properties of a source. Unspecified properties will remain unchanged. +* +* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. +* @param {String} `item` The name of the source. +* @param {int} `position.x` The new x position of the source. +* @param {int} `position.y` The new y position of the source. +* @param {int} `position.alignment` The new alignment of the source. +* @param {double} `rotation` The new clockwise rotation of the item in degrees. +* @param {double} `scale.x` The new x scale of the item. +* @param {double} `scale.y` The new y scale of the item. +* @param {int} `crop.top` The new amount of pixels cropped off the top of the source before scaling. +* @param {int} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling. +* @param {int} `crop.left` The new amount of pixels cropped off the left of the source before scaling. +* @param {int} `crop.right` The new amount of pixels cropped off the right of the source before scaling. +* @param {bool} `visible` The new visibility of the source. 'true' shows source, 'false' hides source. +* @param {String} `bounds.type` The new bounds type of the source. +* @param {int} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10) +* @param {double} `bounds.x` The new width of the bounding box. +* @param {double} `bounds.y` The new height of the bounding box. +* +* @api requests +* @name SetSceneItemProperties +* @category scene items +* @since unreleased +*/ +void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) { + if (!req->hasField("item")) { + req->SendErrorResponse("missing request parameters"); + return; + } + + QString itemName = obs_data_get_string(req->data, "item"); + if (itemName.isEmpty()) { + req->SendErrorResponse("invalid request parameters"); + return; + } + + QString sceneName = obs_data_get_string(req->data, "scene-name"); + OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); + if (!scene) { + req->SendErrorResponse("requested scene doesn't exist"); + return; + } + + OBSSceneItemAutoRelease sceneItem = + Utils::GetSceneItemFromName(scene, itemName); + if (!sceneItem) { + req->SendErrorResponse("specified scene item doesn't exist"); + return; + } + + bool badRequest = false; + OBSDataAutoRelease errorMessage = obs_data_create(); + + if (req->hasField("position")) { + vec2 oldPosition; + OBSDataAutoRelease positionError = obs_data_create(); + obs_sceneitem_get_pos(sceneItem, &oldPosition); + OBSDataAutoRelease reqPosition = obs_data_get_obj(req->data, "position"); + vec2 newPosition = oldPosition; + if (obs_data_has_user_value(reqPosition, "x")) { + newPosition.x = obs_data_get_int(reqPosition, "x"); + } + if (obs_data_has_user_value(reqPosition, "y")) { + newPosition.y = obs_data_get_int(reqPosition, "y"); + } + if (obs_data_has_user_value(reqPosition, "alignment")) { + const uint32_t alignment = obs_data_get_int(reqPosition, "alignment"); + if (Utils::IsValidAlignment(alignment)) { + obs_sceneitem_set_alignment(sceneItem, alignment); + } + else { + badRequest = true; + obs_data_set_string(positionError, "alignment", "invalid"); + obs_data_set_obj(errorMessage, "position", positionError); + } + } + obs_sceneitem_set_pos(sceneItem, &newPosition); + } + + if (req->hasField("rotation")) { + obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(req->data, "rotation")); + } + + if (req->hasField("scale")) { + vec2 oldScale; + obs_sceneitem_get_scale(sceneItem, &oldScale); + OBSDataAutoRelease reqScale = obs_data_get_obj(req->data, "scale"); + vec2 newScale = oldScale; + if (obs_data_has_user_value(reqScale, "x")) { + newScale.x = obs_data_get_double(reqScale, "x"); + } + if (obs_data_has_user_value(reqScale, "y")) { + newScale.y = obs_data_get_double(reqScale, "y"); + } + obs_sceneitem_set_scale(sceneItem, &newScale); + } + + if (req->hasField("crop")) { + obs_sceneitem_crop oldCrop; + obs_sceneitem_get_crop(sceneItem, &oldCrop); + OBSDataAutoRelease reqCrop = obs_data_get_obj(req->data, "crop"); + obs_sceneitem_crop newCrop = oldCrop; + if (obs_data_has_user_value(reqCrop, "top")) { + newCrop.top = obs_data_get_int(reqCrop, "top"); + } + if (obs_data_has_user_value(reqCrop, "right")) { + newCrop.right = obs_data_get_int(reqCrop, "right"); + } + if (obs_data_has_user_value(reqCrop, "bottom")) { + newCrop.bottom = obs_data_get_int(reqCrop, "bottom"); + } + if (obs_data_has_user_value(reqCrop, "left")) { + newCrop.left = obs_data_get_int(reqCrop, "left"); + } + obs_sceneitem_set_crop(sceneItem, &newCrop); + } + + if (req->hasField("visible")) { + obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible")); + } + + if (req->hasField("bounds")) { + bool badBounds = false; + OBSDataAutoRelease boundsError = obs_data_create(); + OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds"); + if (obs_data_has_user_value(reqBounds, "type")) { + const char* newBoundsType = obs_data_get_string(reqBounds, "type"); + if (newBoundsType == "OBS_BOUNDS_NONE") { + obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE); + } + else if (newBoundsType == "OBS_BOUNDS_STRETCH") { + obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_STRETCH); + } + else if (newBoundsType == "OBS_BOUNDS_SCALE_INNER") { + obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_INNER); + } + else if (newBoundsType == "OBS_BOUNDS_SCALE_OUTER") { + obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_OUTER); + } + else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_WIDTH") { + obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_WIDTH); + } + else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_HEIGHT") { + obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_HEIGHT); + } + else if (newBoundsType == "OBS_BOUNDS_MAX_ONLY") { + obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_MAX_ONLY); + } + else { + badRequest = badBounds = true; + obs_data_set_string(boundsError, "type", "invalid"); + } + } + vec2 oldBounds; + obs_sceneitem_get_bounds(sceneItem, &oldBounds); + vec2 newBounds = oldBounds; + if (obs_data_has_user_value(reqBounds, "x")) { + newBounds.x = obs_data_get_double(reqBounds, "x"); + } + if (obs_data_has_user_value(reqBounds, "y")) { + newBounds.y = obs_data_get_double(reqBounds, "y"); + } + obs_sceneitem_set_bounds(sceneItem, &newBounds); + if (obs_data_has_user_value(reqBounds, "alignment")) { + const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment"); + if (Utils::IsValidAlignment(bounds_alignment)) { + obs_sceneitem_set_bounds_alignment(sceneItem, bounds_alignment); + } + else { + badRequest = badBounds = true; + obs_data_set_string(boundsError, "alignment", "invalid"); + } + } + if (badBounds) { + obs_data_set_obj(errorMessage, "bounds", boundsError); + } + } + + if (badRequest) { + req->SendErrorResponse(errorMessage); + } + else { + req->SendOKResponse(); + } +} + +/** +* Reset a scene item. +* +* @param {String (optional)} `scene-name` Name of the scene the source belogns to. Defaults to the current scene. +* @param {String} `item` Name of the source item. +* +* @api requests +* @name ResetSceneItem +* @category scene items +* @since 4.2.0 +*/ +void WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) { + // TODO: remove this request, or refactor it to ResetSource + + if (!req->hasField("item")) { + req->SendErrorResponse("missing request parameters"); + return; + } + + const char* itemName = obs_data_get_string(req->data, "item"); + if (!itemName) { + req->SendErrorResponse("invalid request parameters"); + return; + } + + const char* sceneName = obs_data_get_string(req->data, "scene-name"); + OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); + if (!scene) { + req->SendErrorResponse("requested scene doesn't exist"); + return; + } + + OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); + if (sceneItem) { + OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem); + + OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource); + obs_source_update(sceneItemSource, settings); + + req->SendOKResponse(); + } + else { + req->SendErrorResponse("specified scene item doesn't exist"); + } +} + +/** +* Show or hide a specified source item in a specified scene. +* +* @param {String} `source` Scene item name in the specified scene. +* @param {boolean} `render` true = shown ; false = hidden +* @param {String (optional)} `scene-name` Name of the scene where the source resides. Defaults to the currently active scene. +* +* @api requests +* @name SetSceneItemRender +* @category scene items +* @since 0.3 +* @deprecated Since unreleased. Prefer the use of SetSceneItemProperties. +*/ +void WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) { + if (!req->hasField("source") || + !req->hasField("render")) + { + req->SendErrorResponse("missing request parameters"); + return; + } + + const char* itemName = obs_data_get_string(req->data, "source"); + bool isVisible = obs_data_get_bool(req->data, "render"); + + if (!itemName) { + req->SendErrorResponse("invalid request parameters"); + return; + } + + const char* sceneName = obs_data_get_string(req->data, "scene-name"); + OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); + if (!scene) { + req->SendErrorResponse("requested scene doesn't exist"); + return; + } + + OBSSceneItemAutoRelease sceneItem = + Utils::GetSceneItemFromName(scene, itemName); + if (sceneItem) { + obs_sceneitem_set_visible(sceneItem, isVisible); + req->SendOKResponse(); + } + else { + req->SendErrorResponse("specified scene item doesn't exist"); + } +} + +/** +* Sets the coordinates of a specified source item. +* +* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene. +* @param {String} `item` The name of the source item. +* @param {double} `x` X coordinate. +* @param {double} `y` Y coordinate. + +* +* @api requests +* @name SetSceneItemPosition +* @category scene items +* @since 4.0.0 +* @deprecated Since unreleased. Prefer the use of SetSceneItemProperties. +*/ +void WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) { + if (!req->hasField("item") || + !req->hasField("x") || !req->hasField("y")) { + req->SendErrorResponse("missing request parameters"); + return; + } + + QString itemName = obs_data_get_string(req->data, "item"); + if (itemName.isEmpty()) { + req->SendErrorResponse("invalid request parameters"); + return; + } + + QString sceneName = obs_data_get_string(req->data, "scene-name"); + OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); + if (!scene) { + req->SendErrorResponse("requested scene could not be found"); + return; + } + + OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName); + if (sceneItem) { + vec2 item_position = { 0 }; + item_position.x = obs_data_get_double(req->data, "x"); + item_position.y = obs_data_get_double(req->data, "y"); + obs_sceneitem_set_pos(sceneItem, &item_position); + + req->SendOKResponse(); + } + else { + req->SendErrorResponse("specified scene item doesn't exist"); + } +} + +/** +* Set the transform of the specified source item. +* +* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene. +* @param {String} `item` The name of the source item. +* @param {double} `x-scale` Width scale factor. +* @param {double} `y-scale` Height scale factor. +* @param {double} `rotation` Source item rotation (in degrees). +* +* @api requests +* @name SetSceneItemTransform +* @category scene items +* @since 4.0.0 +* @deprecated Since unreleased. Prefer the use of SetSceneItemProperties. +*/ +void WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) { + if (!req->hasField("item") || + !req->hasField("x-scale") || + !req->hasField("y-scale") || + !req->hasField("rotation")) + { + req->SendErrorResponse("missing request parameters"); + return; + } + + QString itemName = obs_data_get_string(req->data, "item"); + if (itemName.isEmpty()) { + req->SendErrorResponse("invalid request parameters"); + return; + } + + QString sceneName = obs_data_get_string(req->data, "scene-name"); + OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); + if (!scene) { + req->SendErrorResponse("requested scene doesn't exist"); + return; + } + + vec2 scale; + scale.x = obs_data_get_double(req->data, "x-scale"); + scale.y = obs_data_get_double(req->data, "y-scale"); + float rotation = obs_data_get_double(req->data, "rotation"); + + OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); + if (sceneItem) { + obs_sceneitem_set_scale(sceneItem, &scale); + obs_sceneitem_set_rot(sceneItem, rotation); + req->SendOKResponse(); + } + else { + req->SendErrorResponse("specified scene item doesn't exist"); + } +} + +/** +* Sets the crop coordinates of the specified source item. +* +* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. +* @param {String} `item` The name of the source. +* @param {int} `top` Pixel position of the top of the source item. +* @param {int} `bottom` Pixel position of the bottom of the source item. +* @param {int} `left` Pixel position of the left of the source item. +* @param {int} `right` Pixel position of the right of the source item. +* +* @api requests +* @name SetSceneItemCrop +* @category scene items +* @since 4.1.0 +* @deprecated Since unreleased. Prefer the use of SetSceneItemProperties. +*/ +void WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) { + if (!req->hasField("item")) { + req->SendErrorResponse("missing request parameters"); + return; + } + + QString itemName = obs_data_get_string(req->data, "item"); + if (itemName.isEmpty()) { + req->SendErrorResponse("invalid request parameters"); + return; + } + + QString sceneName = obs_data_get_string(req->data, "scene-name"); + OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); + if (!scene) { + req->SendErrorResponse("requested scene doesn't exist"); + return; + } + + OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); + if (sceneItem) { + struct obs_sceneitem_crop crop = { 0 }; + crop.top = obs_data_get_int(req->data, "top"); + crop.bottom = obs_data_get_int(req->data, "bottom"); + crop.left = obs_data_get_int(req->data, "left"); + crop.right = obs_data_get_int(req->data, "right"); + + obs_sceneitem_set_crop(sceneItem, &crop); + + req->SendOKResponse(); + } + else { + req->SendErrorResponse("specified scene item doesn't exist"); + } +} diff --git a/src/WSRequestHandler_Sources.cpp b/src/WSRequestHandler_Sources.cpp index d15398f4..0609bfcf 100644 --- a/src/WSRequestHandler_Sources.cpp +++ b/src/WSRequestHandler_Sources.cpp @@ -4,48 +4,173 @@ #include "WSRequestHandler.h" /** - * Show or hide a specified source item in a specified scene. - * - * @param {String} `source` Name of the source in the specified scene. - * @param {boolean} `render` Desired visibility. - * @param {String (optional)} `scene-name` Name of the scene where the source resides. Defaults to the currently active scene. - * - * @api requests - * @name SetSourceRender - * @category sources - * @since 0.3 - * @deprecated Since unreleased. Prefer the use of SetSceneItemProperties. - */ - void WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) { - if (!req->hasField("source") || - !req->hasField("render")) - { +* List all sources available in the running OBS instance +* +* @return {Array of Objects} `sources` Array of sources as objects +* @return {String} `sources.*.name` Unique source name +* @return {String} `sources.*.typeId` Non-unique source internal type (a.k.a type id) +* @return {String} `sources.*.type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" +* +* @api requests +* @name GetSourcesList +* @category sources +* @since unreleased +*/ +void WSRequestHandler::HandleGetSourcesList(WSRequestHandler* req) { + OBSDataArrayAutoRelease sourcesArray = obs_data_array_create(); + + auto sourceEnumProc = [](void* privateData, obs_source_t* source) -> bool { + obs_data_array_t* sourcesArray = (obs_data_array_t*)privateData; + + OBSDataAutoRelease sourceData = obs_data_create(); + obs_data_set_string(sourceData, "name", obs_source_get_name(source)); + obs_data_set_string(sourceData, "typeId", obs_source_get_id(source)); + + QString typeString = ""; + enum obs_source_type sourceType = obs_source_get_type(source); + switch (sourceType) { + case OBS_SOURCE_TYPE_INPUT: + typeString = "input"; + break; + + case OBS_SOURCE_TYPE_FILTER: + typeString = "filter"; + break; + + case OBS_SOURCE_TYPE_TRANSITION: + typeString = "transition"; + break; + + case OBS_SOURCE_TYPE_SCENE: + typeString = "scene"; + break; + + default: + typeString = "unknown"; + break; + } + obs_data_set_string(sourceData, "type", typeString.toUtf8()); + + obs_data_array_push_back(sourcesArray, sourceData); + return true; + }; + obs_enum_sources(sourceEnumProc, sourcesArray); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_array(response, "sources", sourcesArray); + req->SendOKResponse(response); +} + +/** +* Get a list of all available sources types +* +* @return {Array of Objects} `ids` Array of sources as objects +* @return {String} `ids.*.typeId` Non-unique internal source type ID +* @return {String} `ids.*.displayName` Display name of the source type +* @return {String} `ids.*.type` Type. Value is one of the following: "input", "filter", "transition" or "other" +* @return {Object} `ids.*.defaultSettings` Default settings of this source type +* @return {Object} `ids.*.caps` Source type capabilities +* @return {Boolean} `ids.*.caps.isAsync` True if source of this type provide frames asynchronously +* @return {Boolean} `ids.*.caps.hasVideo` True if sources of this type provide video +* @return {Boolean} `ids.*.caps.hasAudio` True if sources of this type provide audio +* @return {Boolean} `ids.*.caps.canInteract` True if interaction with this sources of this type is possible +* @return {Boolean} `ids.*.caps.isComposite` True if sources of this type composite one or more sub-sources +* @return {Boolean} `ids.*.caps.doNotDuplicate` True if sources of this type should not be fully duplicated +* @return {Boolean} `ids.*.caps.doNotSelfMonitor` True if sources of this type may cause a feedback loop if it's audio is monitored and shouldn't be +* +* @api requests +* @name GetSourcesTypesList +* @category sources +* @since unreleased +*/ +void WSRequestHandler::HandleGetSourceTypesList(WSRequestHandler* req) { + OBSDataArrayAutoRelease idsArray = obs_data_array_create(); + + const char* id; + size_t idx = 0; + + QHash idTypes; + + idx = 0; + while (obs_enum_input_types(idx++, &id)) { + idTypes.insert(id, "input"); + } + + idx = 0; + while (obs_enum_filter_types(idx++, &id)) { + idTypes.insert(id, "filter"); + } + + idx = 0; + while (obs_enum_transition_types(idx++, &id)) { + idTypes.insert(id, "transition"); + } + + idx = 0; + while (obs_enum_source_types(idx++, &id)) { + OBSDataAutoRelease item = obs_data_create(); + + obs_data_set_string(item, "typeId", id); + obs_data_set_string(item, "displayName", obs_source_get_display_name(id)); + obs_data_set_string(item, "type", idTypes.value(id, "other").toUtf8()); + + uint32_t caps = obs_get_source_output_flags(id); + OBSDataAutoRelease capsData = obs_data_create(); + obs_data_set_bool(capsData, "isAsync", caps & OBS_SOURCE_ASYNC); + obs_data_set_bool(capsData, "hasVideo", caps & OBS_SOURCE_VIDEO); + obs_data_set_bool(capsData, "hasAudio", caps & OBS_SOURCE_AUDIO); + obs_data_set_bool(capsData, "canInteract", caps & OBS_SOURCE_INTERACTION); + obs_data_set_bool(capsData, "isComposite", caps & OBS_SOURCE_COMPOSITE); + obs_data_set_bool(capsData, "doNotDuplicate", caps & OBS_SOURCE_DO_NOT_DUPLICATE); + obs_data_set_bool(capsData, "doNotSelfMonitor", caps & OBS_SOURCE_DO_NOT_SELF_MONITOR); + obs_data_set_bool(capsData, "isDeprecated", caps & OBS_SOURCE_DEPRECATED); + + obs_data_set_obj(item, "caps", capsData); + + OBSDataAutoRelease defaultSettings = obs_get_source_defaults(id); + obs_data_set_obj(item, "defaultSettings", defaultSettings); + + obs_data_array_push_back(idsArray, item); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_array(response, "types", idsArray); + req->SendOKResponse(response); +} + +/** +* Get the volume of the specified source. +* +* @param {String} `source` Name of the source. +* +* @return {String} `name` Name of the source. +* @return {double} `volume` Volume of the source. Between `0.0` and `1.0`. +* @return {boolean} `mute` Indicates whether the source is muted. +* +* @api requests +* @name GetVolume +* @category sources +* @since 4.0.0 +*/ +void WSRequestHandler::HandleGetVolume(WSRequestHandler* req) { + if (!req->hasField("source")) { req->SendErrorResponse("missing request parameters"); return; } - const char* itemName = obs_data_get_string(req->data, "source"); - bool isVisible = obs_data_get_bool(req->data, "render"); + QString sourceName = obs_data_get_string(req->data, "source"); + if (!sourceName.isEmpty()) { + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); - if (!itemName) { + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "name", sourceName.toUtf8()); + obs_data_set_double(response, "volume", obs_source_get_volume(source)); + obs_data_set_bool(response, "muted", obs_source_muted(source)); + + req->SendOKResponse(response); + } + else { req->SendErrorResponse("invalid request parameters"); - return; - } - - const char* sceneName = obs_data_get_string(req->data, "scene-name"); - OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); - if (!scene) { - req->SendErrorResponse("requested scene doesn't exist"); - return; - } - - OBSSceneItemAutoRelease sceneItem = - Utils::GetSceneItemFromName(scene, itemName); - if (sceneItem) { - obs_sceneitem_set_visible(sceneItem, isVisible); - req->SendOKResponse(); - } else { - req->SendErrorResponse("specified scene item doesn't exist"); } } @@ -88,53 +213,21 @@ } /** - * Get the volume of the specified source. - * - * @param {String} `source` Name of the source. - * - * @return {String} `name` Name of the source. - * @return {double} `volume` Volume of the source. Between `0.0` and `1.0`. - * @return {boolean} `mute` Indicates whether the source is muted. - * - * @api requests - * @name GetVolume - * @category sources - * @since 4.0.0 - */ -void WSRequestHandler::HandleGetVolume(WSRequestHandler* req) { +* Get the mute status of a specified source. +* +* @param {String} `source` The name of the source. +* +* @return {String} `name` The name of the source. +* @return {boolean} `muted` Mute status of the source. +* +* @api requests +* @name GetMute +* @category sources +* @since 4.0.0 +*/ +void WSRequestHandler::HandleGetMute(WSRequestHandler* req) { if (!req->hasField("source")) { - req->SendErrorResponse("missing request parameters"); - return; - } - - QString sourceName = obs_data_get_string(req->data, "source"); - if (!sourceName.isEmpty()) { - OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); - - OBSDataAutoRelease response = obs_data_create(); - obs_data_set_string(response, "name", sourceName.toUtf8()); - obs_data_set_double(response, "volume", obs_source_get_volume(source)); - obs_data_set_bool(response, "muted", obs_source_muted(source)); - - req->SendOKResponse(response); - } else { - req->SendErrorResponse("invalid request parameters"); - } -} - -/** - * Inverts the mute status of a specified source. - * - * @param {String} `source` The name of the source. - * - * @api requests - * @name ToggleMute - * @category sources - * @since 4.0.0 - */ -void WSRequestHandler::HandleToggleMute(WSRequestHandler* req) { - if (!req->hasField("source")) { - req->SendErrorResponse("missing request parameters"); + req->SendErrorResponse("mssing request parameters"); return; } @@ -146,12 +239,15 @@ void WSRequestHandler::HandleToggleMute(WSRequestHandler* req) { OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { - req->SendErrorResponse("invalid request parameters"); + req->SendErrorResponse("specified source doesn't exist"); return; } - obs_source_set_muted(source, !obs_source_muted(source)); - req->SendOKResponse(); + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "name", obs_source_get_name(source)); + obs_data_set_bool(response, "muted", obs_source_muted(source)); + + req->SendOKResponse(response); } /** @@ -191,21 +287,18 @@ void WSRequestHandler::HandleSetMute(WSRequestHandler* req) { } /** - * Get the mute status of a specified source. - * - * @param {String} `source` The name of the source. - * - * @return {String} `name` The name of the source. - * @return {boolean} `muted` Mute status of the source. - * - * @api requests - * @name GetMute - * @category sources - * @since 4.0.0 - */ -void WSRequestHandler::HandleGetMute(WSRequestHandler* req) { +* Inverts the mute status of a specified source. +* +* @param {String} `source` The name of the source. +* +* @api requests +* @name ToggleMute +* @category sources +* @since 4.0.0 +*/ +void WSRequestHandler::HandleToggleMute(WSRequestHandler* req) { if (!req->hasField("source")) { - req->SendErrorResponse("mssing request parameters"); + req->SendErrorResponse("missing request parameters"); return; } @@ -217,15 +310,12 @@ void WSRequestHandler::HandleGetMute(WSRequestHandler* req) { OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); if (!source) { - req->SendErrorResponse("specified source doesn't exist"); + req->SendErrorResponse("invalid request parameters"); return; } - OBSDataAutoRelease response = obs_data_create(); - obs_data_set_string(response, "name", obs_source_get_name(source)); - obs_data_set_bool(response, "muted", obs_source_muted(source)); - - req->SendOKResponse(response); + obs_source_set_muted(source, !obs_source_muted(source)); + req->SendOKResponse(); } /** @@ -297,468 +387,106 @@ void WSRequestHandler::HandleGetSyncOffset(WSRequestHandler* req) { } /** - * Sets the coordinates of a specified source item. - * - * @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene. - * @param {String} `item` The name of the source item. - * @param {double} `x` X coordinate. - * @param {double} `y` Y coordinate. - - * - * @api requests - * @name SetSceneItemPosition - * @category sources - * @since 4.0.0 - * @deprecated Since unreleased. Prefer the use of SetSceneItemProperties. - */ - void WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) { - if (!req->hasField("item") || - !req->hasField("x") || !req->hasField("y")) { +* Get settings of the specified source +* +* @param {String} `sourceName` Name of the source item. +* @param {String (optional)} `sourceType` Type of the specified source. Useful for type-checking if you expect a specific settings schema. +* +* @return {String} `sourceName` Source name +* @return {String} `sourceType` Type of the specified source +* @return {Object} `sourceSettings` Source settings. Varying between source types. +* +* @api requests +* @name GetSourceSettings +* @category sources +* @since unreleased +*/ +void WSRequestHandler::HandleGetSourceSettings(WSRequestHandler* req) { + if (!req->hasField("sourceName")) { req->SendErrorResponse("missing request parameters"); return; } - QString itemName = obs_data_get_string(req->data, "item"); - if (itemName.isEmpty()) { - req->SendErrorResponse("invalid request parameters"); + const char* sourceName = obs_data_get_string(req->data, "sourceName"); + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); + if (!source) { + req->SendErrorResponse("specified source doesn't exist"); return; } - QString sceneName = obs_data_get_string(req->data, "scene-name"); - OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); - if (!scene) { - req->SendErrorResponse("requested scene could not be found"); - return; + if (req->hasField("sourceType")) { + QString actualSourceType = obs_source_get_id(source); + QString requestedType = obs_data_get_string(req->data, "sourceType"); + + if (actualSourceType != requestedType) { + req->SendErrorResponse("specified source exists but is not of expected type"); + return; + } } - OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName); - if (sceneItem) { - vec2 item_position = { 0 }; - item_position.x = obs_data_get_double(req->data, "x"); - item_position.y = obs_data_get_double(req->data, "y"); - obs_sceneitem_set_pos(sceneItem, &item_position); + OBSDataAutoRelease sourceSettings = obs_source_get_settings(source); - req->SendOKResponse(); - } else { - req->SendErrorResponse("specified scene item doesn't exist"); - } + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "sourceName", obs_source_get_name(source)); + obs_data_set_string(response, "sourceType", obs_source_get_id(source)); + obs_data_set_obj(response, "sourceSettings", sourceSettings); + req->SendOKResponse(response); } /** - * Set the transform of the specified source item. - * - * @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene. - * @param {String} `item` The name of the source item. - * @param {double} `x-scale` Width scale factor. - * @param {double} `y-scale` Height scale factor. - * @param {double} `rotation` Source item rotation (in degrees). - * - * @api requests - * @name SetSceneItemTransform - * @category sources - * @since 4.0.0 - * @deprecated Since unreleased. Prefer the use of SetSceneItemProperties. - */ -void WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) { - if (!req->hasField("item") || - !req->hasField("x-scale") || - !req->hasField("y-scale") || - !req->hasField("rotation")) - { +* Set settings of the specified source. +* +* @param {String} `sourceName` Name of the source item. +* @param {String (optional)} `sourceType` Type of the specified source. Useful for type-checking to avoid settings a set of settings incompatible with the actual source's type. +* @param {Object} `sourceSettings` Source settings. Varying between source types. +* +* @return {String} `sourceName` Source name +* @return {String} `sourceType` Type of the specified source +* @return {Object} `sourceSettings` Source settings. Varying between source types. +* +* @api requests +* @name SetSourceSettings +* @category sources +* @since unreleased +*/ +void WSRequestHandler::HandleSetSourceSettings(WSRequestHandler* req) { + if (!req->hasField("sourceName") || !req->hasField("sourceSettings")) { req->SendErrorResponse("missing request parameters"); return; } - QString itemName = obs_data_get_string(req->data, "item"); - if (itemName.isEmpty()) { - req->SendErrorResponse("invalid request parameters"); + const char* sourceName = obs_data_get_string(req->data, "sourceName"); + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); + if (!source) { + req->SendErrorResponse("specified source doesn't exist"); return; } - QString sceneName = obs_data_get_string(req->data, "scene-name"); - OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); - if (!scene) { - req->SendErrorResponse("requested scene doesn't exist"); - return; - } + if (req->hasField("sourceType")) { + QString actualSourceType = obs_source_get_id(source); + QString requestedType = obs_data_get_string(req->data, "sourceType"); - vec2 scale; - scale.x = obs_data_get_double(req->data, "x-scale"); - scale.y = obs_data_get_double(req->data, "y-scale"); - float rotation = obs_data_get_double(req->data, "rotation"); - - OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); - if (sceneItem) { - obs_sceneitem_set_scale(sceneItem, &scale); - obs_sceneitem_set_rot(sceneItem, rotation); - req->SendOKResponse(); - } else { - req->SendErrorResponse("specified scene item doesn't exist"); - } -} - -/** - * Sets the crop coordinates of the specified source item. - * - * @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. - * @param {String} `item` The name of the source. - * @param {int} `top` Pixel position of the top of the source item. - * @param {int} `bottom` Pixel position of the bottom of the source item. - * @param {int} `left` Pixel position of the left of the source item. - * @param {int} `right` Pixel position of the right of the source item. - * - * @api requests - * @name SetSceneItemCrop - * @category sources - * @since 4.1.0 - * @deprecated Since unreleased. Prefer the use of SetSceneItemProperties. - */ -void WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) { - if (!req->hasField("item")) { - req->SendErrorResponse("missing request parameters"); - return; - } - - QString itemName = obs_data_get_string(req->data, "item"); - if (itemName.isEmpty()) { - req->SendErrorResponse("invalid request parameters"); - return; - } - - QString sceneName = obs_data_get_string(req->data, "scene-name"); - OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); - if (!scene) { - req->SendErrorResponse("requested scene doesn't exist"); - return; - } - - OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); - if (sceneItem) { - struct obs_sceneitem_crop crop = { 0 }; - crop.top = obs_data_get_int(req->data, "top"); - crop.bottom = obs_data_get_int(req->data, "bottom"); - crop.left = obs_data_get_int(req->data, "left"); - crop.right = obs_data_get_int(req->data, "right"); - - obs_sceneitem_set_crop(sceneItem, &crop); - - req->SendOKResponse(); - } else { - req->SendErrorResponse("specified scene item doesn't exist"); - } -} - -/** - * Gets the scene specific properties of the specified source item. - * - * @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. - * @param {String} `item` The name of the source. - * - * @return {String} `name` The name of the source. - * @return {int} `position.x` The x position of the source from the left. - * @return {int} `position.y` The y position of the source from the top. - * @return {int} `position.alignment` The point on the source that the item is manipulated from. - * @return {double} `rotation` The clockwise rotation of the item in degrees around the point of alignment. - * @return {double} `scale.x` The x-scale factor of the source. - * @return {double} `scale.y` The y-scale factor of the source. - * @return {int} `crop.top` The number of pixels cropped off the top of the source before scaling. - * @return {int} `crop.right` The number of pixels cropped off the right of the source before scaling. - * @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling. - * @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling. - * @return {bool} `visible` If the source is visible. - * @return {String} `bounds.type` Type of bounding box. - * @return {int} `bounds.alignment` Alignment of the bounding box. - * @return {double} `bounds.x` Width of the bounding box. - * @return {double} `bounds.y` Height of the bounding box. - * - * @api requests - * @name GetSceneItemProperties - * @category sources - * @since unreleased - */ -void WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) { - if (!req->hasField("item")) { - req->SendErrorResponse("missing request parameters"); - return; - } - - QString itemName = obs_data_get_string(req->data, "item"); - if (itemName.isEmpty()) { - req->SendErrorResponse("invalid request parameters"); - return; - } - - QString sceneName = obs_data_get_string(req->data, "scene-name"); - OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); - if (!scene) { - req->SendErrorResponse("requested scene doesn't exist"); - return; - } - - OBSSceneItemAutoRelease sceneItem = - Utils::GetSceneItemFromName(scene, itemName); - if (!sceneItem) { - req->SendErrorResponse("specified scene item doesn't exist"); - return; - } - - OBSDataAutoRelease data = obs_data_create(); - obs_data_set_string(data, "name", itemName.toUtf8()); - - OBSDataAutoRelease posData = obs_data_create(); - vec2 pos; - obs_sceneitem_get_pos(sceneItem, &pos); - obs_data_set_double(posData, "x", pos.x); - obs_data_set_double(posData, "y", pos.y); - obs_data_set_int(posData, "alignment", obs_sceneitem_get_alignment(sceneItem)); - obs_data_set_obj(data, "position", posData); - - obs_data_set_double(data, "rotation", obs_sceneitem_get_rot(sceneItem)); - - OBSDataAutoRelease scaleData = obs_data_create(); - vec2 scale; - obs_sceneitem_get_scale(sceneItem, &scale); - obs_data_set_double(scaleData, "x", scale.x); - obs_data_set_double(scaleData, "y", scale.y); - obs_data_set_obj(data, "scale", scaleData); - - OBSDataAutoRelease cropData = obs_data_create(); - obs_sceneitem_crop crop; - obs_sceneitem_get_crop(sceneItem, &crop); - obs_data_set_int(cropData, "left", crop.left); - obs_data_set_int(cropData, "top", crop.top); - obs_data_set_int(cropData, "right", crop.right); - obs_data_set_int(cropData, "bottom", crop.bottom); - obs_data_set_obj(data, "crop", cropData); - - obs_data_set_bool(data, "visible", obs_sceneitem_visible(sceneItem)); - - OBSDataAutoRelease boundsData = obs_data_create(); - obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem); - if (boundsType == OBS_BOUNDS_NONE) { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_NONE"); - } - else { - switch(boundsType) { - case OBS_BOUNDS_STRETCH: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_STRETCH"); - break; - } - case OBS_BOUNDS_SCALE_INNER: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_INNER"); - break; - } - case OBS_BOUNDS_SCALE_OUTER: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_OUTER"); - break; - } - case OBS_BOUNDS_SCALE_TO_WIDTH: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_WIDTH"); - break; - } - case OBS_BOUNDS_SCALE_TO_HEIGHT: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_HEIGHT"); - break; - } - case OBS_BOUNDS_MAX_ONLY: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_MAX_ONLY"); - break; - } - } - obs_data_set_int(boundsData, "alignment", obs_sceneitem_get_bounds_alignment(sceneItem)); - vec2 bounds; - obs_sceneitem_get_bounds(sceneItem, &bounds); - obs_data_set_double(boundsData, "x", bounds.x); - obs_data_set_double(boundsData, "y", bounds.y); - } - obs_data_set_obj(data, "bounds", boundsData); - - req->SendOKResponse(data); -} - -/** - * Sets the scene specific properties of a source. Unspecified properties will remain unchanged. - * - * @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. - * @param {String} `item` The name of the source. - * @param {int} `position.x` The new x position of the source. - * @param {int} `position.y` The new y position of the source. - * @param {int} `position.alignment` The new alignment of the source. - * @param {double} `rotation` The new clockwise rotation of the item in degrees. - * @param {double} `scale.x` The new x scale of the item. - * @param {double} `scale.y` The new y scale of the item. - * @param {int} `crop.top` The new amount of pixels cropped off the top of the source before scaling. - * @param {int} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling. - * @param {int} `crop.left` The new amount of pixels cropped off the left of the source before scaling. - * @param {int} `crop.right` The new amount of pixels cropped off the right of the source before scaling. - * @param {bool} `visible` The new visibility of the source. 'true' shows source, 'false' hides source. - * @param {String} `bounds.type` The new bounds type of the source. - * @param {int} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10) - * @param {double} `bounds.x` The new width of the bounding box. - * @param {double} `bounds.y` The new height of the bounding box. - * - * @api requests - * @name SetSceneItemProperties - * @category sources - * @since unreleased - */ -void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) { - if (!req->hasField("item")) { - req->SendErrorResponse("missing request parameters"); - return; - } - - QString itemName = obs_data_get_string(req->data, "item"); - if (itemName.isEmpty()) { - req->SendErrorResponse("invalid request parameters"); - return; - } - - QString sceneName = obs_data_get_string(req->data, "scene-name"); - OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); - if (!scene) { - req->SendErrorResponse("requested scene doesn't exist"); - return; - } - - OBSSceneItemAutoRelease sceneItem = - Utils::GetSceneItemFromName(scene, itemName); - if (!sceneItem) { - req->SendErrorResponse("specified scene item doesn't exist"); - return; - } - - bool badRequest = false; - OBSDataAutoRelease errorMessage = obs_data_create(); - - if (req->hasField("position")) { - vec2 oldPosition; - OBSDataAutoRelease positionError = obs_data_create(); - obs_sceneitem_get_pos(sceneItem, &oldPosition); - OBSDataAutoRelease reqPosition = obs_data_get_obj(req->data, "position"); - vec2 newPosition = oldPosition; - if (obs_data_has_user_value(reqPosition, "x")) { - newPosition.x = obs_data_get_int(reqPosition, "x"); - } - if (obs_data_has_user_value(reqPosition, "y")) { - newPosition.y = obs_data_get_int(reqPosition, "y"); - } - if (obs_data_has_user_value(reqPosition, "alignment")) { - const uint32_t alignment = obs_data_get_int(reqPosition, "alignment"); - if (Utils::IsValidAlignment(alignment)) { - obs_sceneitem_set_alignment(sceneItem, alignment); - } else { - badRequest = true; - obs_data_set_string(positionError, "alignment", "invalid"); - obs_data_set_obj(errorMessage, "position", positionError); - } - } - obs_sceneitem_set_pos(sceneItem, &newPosition); - } - - if (req->hasField("rotation")) { - obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(req->data, "rotation")); - } - - if (req->hasField("scale")) { - vec2 oldScale; - obs_sceneitem_get_scale(sceneItem, &oldScale); - OBSDataAutoRelease reqScale = obs_data_get_obj(req->data, "scale"); - vec2 newScale = oldScale; - if (obs_data_has_user_value(reqScale, "x")) { - newScale.x = obs_data_get_double(reqScale, "x"); - } - if (obs_data_has_user_value(reqScale, "y")) { - newScale.y = obs_data_get_double(reqScale, "y"); - } - obs_sceneitem_set_scale(sceneItem, &newScale); - } - - if (req->hasField("crop")) { - obs_sceneitem_crop oldCrop; - obs_sceneitem_get_crop(sceneItem, &oldCrop); - OBSDataAutoRelease reqCrop = obs_data_get_obj(req->data, "crop"); - obs_sceneitem_crop newCrop = oldCrop; - if (obs_data_has_user_value(reqCrop, "top")) { - newCrop.top = obs_data_get_int(reqCrop, "top"); - } - if (obs_data_has_user_value(reqCrop, "right")) { - newCrop.right = obs_data_get_int(reqCrop, "right"); - } - if (obs_data_has_user_value(reqCrop, "bottom")) { - newCrop.bottom = obs_data_get_int(reqCrop, "bottom"); - } - if (obs_data_has_user_value(reqCrop, "left")) { - newCrop.left = obs_data_get_int(reqCrop, "left"); - } - obs_sceneitem_set_crop(sceneItem, &newCrop); - } - - if (req->hasField("visible")) { - obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible")); - } - - if (req->hasField("bounds")) { - bool badBounds = false; - OBSDataAutoRelease boundsError = obs_data_create(); - OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds"); - if (obs_data_has_user_value(reqBounds, "type")) { - const char* newBoundsType = obs_data_get_string(reqBounds, "type"); - if (newBoundsType == "OBS_BOUNDS_NONE") { - obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE); - } - else if (newBoundsType == "OBS_BOUNDS_STRETCH") { - obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_STRETCH); - } - else if (newBoundsType == "OBS_BOUNDS_SCALE_INNER") { - obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_INNER); - } - else if (newBoundsType == "OBS_BOUNDS_SCALE_OUTER") { - obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_OUTER); - } - else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_WIDTH") { - obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_WIDTH); - } - else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_HEIGHT") { - obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_HEIGHT); - } - else if (newBoundsType == "OBS_BOUNDS_MAX_ONLY") { - obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_MAX_ONLY); - } - else { - badRequest = badBounds = true; - obs_data_set_string(boundsError, "type", "invalid"); - } - } - vec2 oldBounds; - obs_sceneitem_get_bounds(sceneItem, &oldBounds); - vec2 newBounds = oldBounds; - if (obs_data_has_user_value(reqBounds, "x")) { - newBounds.x = obs_data_get_double(reqBounds, "x"); - } - if (obs_data_has_user_value(reqBounds, "y")) { - newBounds.y = obs_data_get_double(reqBounds, "y"); - } - obs_sceneitem_set_bounds(sceneItem, &newBounds); - if (obs_data_has_user_value(reqBounds, "alignment")) { - const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment"); - if (Utils::IsValidAlignment(bounds_alignment)) { - obs_sceneitem_set_bounds_alignment(sceneItem, bounds_alignment); - } else { - badRequest = badBounds = true; - obs_data_set_string(boundsError, "alignment", "invalid"); - } - } - if (badBounds) { - obs_data_set_obj(errorMessage, "bounds", boundsError); + if (actualSourceType != requestedType) { + req->SendErrorResponse("specified source exists but is not of expected type"); + return; } } - if (badRequest) { - req->SendErrorResponse(errorMessage); - } else { - req->SendOKResponse(); - } + OBSDataAutoRelease currentSettings = obs_source_get_settings(source); + OBSDataAutoRelease newSettings = obs_data_get_obj(req->data, "sourceSettings"); + + OBSDataAutoRelease sourceSettings = obs_data_create(); + obs_data_apply(sourceSettings, currentSettings); + obs_data_apply(sourceSettings, newSettings); + + obs_source_update(source, sourceSettings); + obs_source_update_properties(source); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "sourceName", obs_source_get_name(source)); + obs_data_set_string(response, "sourceType", obs_source_get_id(source)); + obs_data_set_obj(response, "sourceSettings", sourceSettings); + req->SendOKResponse(response); } /** @@ -802,6 +530,9 @@ void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) { * @since 4.1.0 */ void WSRequestHandler::HandleGetTextGDIPlusProperties(WSRequestHandler* req) { + // TODO: source settings are independent of any scene, so there's no need + // to target a specific scene + const char* itemName = obs_data_get_string(req->data, "source"); if (!itemName) { req->SendErrorResponse("invalid request parameters"); @@ -876,6 +607,9 @@ void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) { * @since 4.1.0 */ void WSRequestHandler::HandleSetTextGDIPlusProperties(WSRequestHandler* req) { + // TODO: source settings are independent of any scene, so there's no need + // to target a specific scene + if (!req->hasField("source")) { req->SendErrorResponse("missing request parameters"); return; @@ -1081,6 +815,9 @@ void WSRequestHandler::HandleSetTextGDIPlusProperties(WSRequestHandler* req) { * @since 4.1.0 */ void WSRequestHandler::HandleGetBrowserSourceProperties(WSRequestHandler* req) { + // TODO: source settings are independent of any scene, so there's no need + // to target a specific scene + const char* itemName = obs_data_get_string(req->data, "source"); if (!itemName) { req->SendErrorResponse("invalid request parameters"); @@ -1135,6 +872,9 @@ void WSRequestHandler::HandleGetBrowserSourceProperties(WSRequestHandler* req) { * @since 4.1.0 */ void WSRequestHandler::HandleSetBrowserSourceProperties(WSRequestHandler* req) { + // TODO: source settings are independent of any scene, so there's no need + // to target a specific scene + if (!req->hasField("source")) { req->SendErrorResponse("missing request parameters"); return; @@ -1217,287 +957,6 @@ void WSRequestHandler::HandleSetBrowserSourceProperties(WSRequestHandler* req) { } } -/** - * Reset a source item. - * - * @param {String (optional)} `scene-name` Name of the scene the source belogns to. Defaults to the current scene. - * @param {String} `item` Name of the source item. - * - * @api requests - * @name ResetSceneItem - * @category sources - * @since 4.2.0 - */ -void WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) { - if (!req->hasField("item")) { - req->SendErrorResponse("missing request parameters"); - return; - } - - const char* itemName = obs_data_get_string(req->data, "item"); - if (!itemName) { - req->SendErrorResponse("invalid request parameters"); - return; - } - - const char* sceneName = obs_data_get_string(req->data, "scene-name"); - OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName); - if (!scene) { - req->SendErrorResponse("requested scene doesn't exist"); - return; - } - - OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); - if (sceneItem) { - OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem); - - OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource); - obs_source_update(sceneItemSource, settings); - - req->SendOKResponse(); - } else { - req->SendErrorResponse("specified scene item doesn't exist"); - } -} - -/** - * List all sources available in the running OBS instance - * - * @return {Array of Objects} `sources` Array of sources as objects - * @return {String} `sources.*.name` Unique source name - * @return {String} `sources.*.typeId` Non-unique source internal type (a.k.a type id) - * @return {String} `sources.*.type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" - * - * @api requests - * @name GetSourcesList - * @category sources - * @since unreleased - */ - void WSRequestHandler::HandleGetSourcesList(WSRequestHandler* req) { - OBSDataArrayAutoRelease sourcesArray = obs_data_array_create(); - - auto sourceEnumProc = [](void* privateData, obs_source_t* source) -> bool { - obs_data_array_t* sourcesArray = (obs_data_array_t*)privateData; - - OBSDataAutoRelease sourceData = obs_data_create(); - obs_data_set_string(sourceData, "name", obs_source_get_name(source)); - obs_data_set_string(sourceData, "typeId", obs_source_get_id(source)); - - QString typeString = ""; - enum obs_source_type sourceType = obs_source_get_type(source); - switch (sourceType) { - case OBS_SOURCE_TYPE_INPUT: - typeString = "input"; - break; - - case OBS_SOURCE_TYPE_FILTER: - typeString = "filter"; - break; - - case OBS_SOURCE_TYPE_TRANSITION: - typeString = "transition"; - break; - - case OBS_SOURCE_TYPE_SCENE: - typeString = "scene"; - break; - - default: - typeString = "unknown"; - break; - } - obs_data_set_string(sourceData, "type", typeString.toUtf8()); - - obs_data_array_push_back(sourcesArray, sourceData); - return true; - }; - obs_enum_sources(sourceEnumProc, sourcesArray); - - OBSDataAutoRelease response = obs_data_create(); - obs_data_set_array(response, "sources", sourcesArray); - req->SendOKResponse(response); -} - -/** -* Get a list of all available sources types -* -* @return {Array of Objects} `ids` Array of sources as objects -* @return {String} `ids.*.typeId` Non-unique internal source type ID -* @return {String} `ids.*.displayName` Display name of the source type -* @return {String} `ids.*.type` Type. Value is one of the following: "input", "filter", "transition" or "other" -* @return {Object} `ids.*.defaultSettings` Default settings of this source type -* @return {Object} `ids.*.caps` Source type capabilities -* @return {Boolean} `ids.*.caps.isAsync` True if source of this type provide frames asynchronously -* @return {Boolean} `ids.*.caps.hasVideo` True if sources of this type provide video -* @return {Boolean} `ids.*.caps.hasAudio` True if sources of this type provide audio -* @return {Boolean} `ids.*.caps.canInteract` True if interaction with this sources of this type is possible -* @return {Boolean} `ids.*.caps.isComposite` True if sources of this type composite one or more sub-sources -* @return {Boolean} `ids.*.caps.doNotDuplicate` True if sources of this type should not be fully duplicated -* @return {Boolean} `ids.*.caps.doNotSelfMonitor` True if sources of this type may cause a feedback loop if it's audio is monitored and shouldn't be -* -* @api requests -* @name GetSourcesTypesList -* @category sources -* @since unreleased -*/ -void WSRequestHandler::HandleGetSourceTypesList(WSRequestHandler* req) { - OBSDataArrayAutoRelease idsArray = obs_data_array_create(); - - const char* id; - size_t idx = 0; - - QHash idTypes; - - idx = 0; - while (obs_enum_input_types(idx++, &id)) { - idTypes.insert(id, "input"); - } - - idx = 0; - while (obs_enum_filter_types(idx++, &id)) { - idTypes.insert(id, "filter"); - } - - idx = 0; - while (obs_enum_transition_types(idx++, &id)) { - idTypes.insert(id, "transition"); - } - - idx = 0; - while (obs_enum_source_types(idx++, &id)) { - OBSDataAutoRelease item = obs_data_create(); - - obs_data_set_string(item, "typeId", id); - obs_data_set_string(item, "displayName", obs_source_get_display_name(id)); - obs_data_set_string(item, "type", idTypes.value(id, "other").toUtf8()); - - uint32_t caps = obs_get_source_output_flags(id); - OBSDataAutoRelease capsData = obs_data_create(); - obs_data_set_bool(capsData, "isAsync", caps & OBS_SOURCE_ASYNC); - obs_data_set_bool(capsData, "hasVideo", caps & OBS_SOURCE_VIDEO); - obs_data_set_bool(capsData, "hasAudio", caps & OBS_SOURCE_AUDIO); - obs_data_set_bool(capsData, "canInteract", caps & OBS_SOURCE_INTERACTION); - obs_data_set_bool(capsData, "isComposite", caps & OBS_SOURCE_COMPOSITE); - obs_data_set_bool(capsData, "doNotDuplicate", caps & OBS_SOURCE_DO_NOT_DUPLICATE); - obs_data_set_bool(capsData, "doNotSelfMonitor", caps & OBS_SOURCE_DO_NOT_SELF_MONITOR); - obs_data_set_bool(capsData, "isDeprecated", caps & OBS_SOURCE_DEPRECATED); - - obs_data_set_obj(item, "caps", capsData); - - OBSDataAutoRelease defaultSettings = obs_get_source_defaults(id); - obs_data_set_obj(item, "defaultSettings", defaultSettings); - - obs_data_array_push_back(idsArray, item); - } - - OBSDataAutoRelease response = obs_data_create(); - obs_data_set_array(response, "types", idsArray); - req->SendOKResponse(response); -} - -/** - * Get settings of the specified source - * - * @param {String} `sourceName` Name of the source item. - * @param {String (optional)} `sourceType` Type of the specified source. Useful for type-checking if you expect a specific settings schema. - * - * @return {String} `sourceName` Source name - * @return {String} `sourceType` Type of the specified source - * @return {Object} `sourceSettings` Source settings. Varying between source types. - * - * @api requests - * @name GetSourceSettings - * @category sources - * @since unreleased - */ - void WSRequestHandler::HandleGetSourceSettings(WSRequestHandler* req) { - if (!req->hasField("sourceName")) { - req->SendErrorResponse("missing request parameters"); - return; - } - - const char* sourceName = obs_data_get_string(req->data, "sourceName"); - OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); - if (!source) { - req->SendErrorResponse("specified source doesn't exist"); - return; - } - - if (req->hasField("sourceType")) { - QString actualSourceType = obs_source_get_id(source); - QString requestedType = obs_data_get_string(req->data, "sourceType"); - - if (actualSourceType != requestedType) { - req->SendErrorResponse("specified source exists but is not of expected type"); - return; - } - } - - OBSDataAutoRelease sourceSettings = obs_source_get_settings(source); - - OBSDataAutoRelease response = obs_data_create(); - obs_data_set_string(response, "sourceName", obs_source_get_name(source)); - obs_data_set_string(response, "sourceType", obs_source_get_id(source)); - obs_data_set_obj(response, "sourceSettings", sourceSettings); - req->SendOKResponse(response); -} - -/** - * Set settings of the specified source. - * - * @param {String} `sourceName` Name of the source item. - * @param {String (optional)} `sourceType` Type of the specified source. Useful for type-checking to avoid settings a set of settings incompatible with the actual source's type. - * @param {Object} `sourceSettings` Source settings. Varying between source types. - * - * @return {String} `sourceName` Source name - * @return {String} `sourceType` Type of the specified source - * @return {Object} `sourceSettings` Source settings. Varying between source types. - * - * @api requests - * @name SetSourceSettings - * @category sources - * @since unreleased - */ -void WSRequestHandler::HandleSetSourceSettings(WSRequestHandler* req) { - if (!req->hasField("sourceName") || !req->hasField("sourceSettings")) { - req->SendErrorResponse("missing request parameters"); - return; - } - - const char* sourceName = obs_data_get_string(req->data, "sourceName"); - OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); - if (!source) { - req->SendErrorResponse("specified source doesn't exist"); - return; - } - - if (req->hasField("sourceType")) { - QString actualSourceType = obs_source_get_id(source); - QString requestedType = obs_data_get_string(req->data, "sourceType"); - - if (actualSourceType != requestedType) { - req->SendErrorResponse("specified source exists but is not of expected type"); - return; - } - } - - OBSDataAutoRelease currentSettings = obs_source_get_settings(source); - OBSDataAutoRelease newSettings = obs_data_get_obj(req->data, "sourceSettings"); - - OBSDataAutoRelease sourceSettings = obs_data_create(); - obs_data_apply(sourceSettings, currentSettings); - obs_data_apply(sourceSettings, newSettings); - - obs_source_update(source, sourceSettings); - obs_source_update_properties(source); - - OBSDataAutoRelease response = obs_data_create(); - obs_data_set_string(response, "sourceName", obs_source_get_name(source)); - obs_data_set_string(response, "sourceType", obs_source_get_id(source)); - obs_data_set_obj(response, "sourceSettings", sourceSettings); - req->SendOKResponse(response); -} - /** * Get configured special sources like Desktop Audio and Mic/Aux sources. *