From 9cdfa41113cc552a29373ba37cd1a86114d3616a Mon Sep 17 00:00:00 2001 From: tt2468 Date: Sat, 2 Jul 2022 08:23:03 -0700 Subject: [PATCH] requesthandler: Add projector creation requests I didn't think I'd be able to make remotely usable requests using OBS' existing projector API, but I'm actually pretty happy with how it turned out. Closes #929 Co-authored-by: Brendan Allan --- src/requesthandler/RequestHandler.cpp | 2 + src/requesthandler/RequestHandler.h | 2 + .../RequestHandler_SceneItems.cpp | 6 +- src/requesthandler/RequestHandler_Ui.cpp | 109 ++++++++++++++++++ src/utils/Obs_ArrayHelper.cpp | 66 +++++------ src/utils/Obs_SearchHelper.cpp | 35 +++--- .../WebSocketServer_Protocol.cpp | 3 +- 7 files changed, 167 insertions(+), 56 deletions(-) diff --git a/src/requesthandler/RequestHandler.cpp b/src/requesthandler/RequestHandler.cpp index 2ef426fc..28858b29 100644 --- a/src/requesthandler/RequestHandler.cpp +++ b/src/requesthandler/RequestHandler.cpp @@ -181,6 +181,8 @@ const std::unordered_map RequestHandler::_han {"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog}, {"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog}, {"GetMonitorList", &RequestHandler::GetMonitorList}, + {"OpenVideoMixProjector", &RequestHandler::OpenVideoMixProjector}, + {"OpenSourceProjector", &RequestHandler::OpenSourceProjector}, }; RequestHandler::RequestHandler(SessionPtr session) : _session(session) {} diff --git a/src/requesthandler/RequestHandler.h b/src/requesthandler/RequestHandler.h index bb94e3c9..fcf57437 100644 --- a/src/requesthandler/RequestHandler.h +++ b/src/requesthandler/RequestHandler.h @@ -200,6 +200,8 @@ private: RequestResult OpenInputFiltersDialog(const Request &); RequestResult OpenInputInteractDialog(const Request &); RequestResult GetMonitorList(const Request &); + RequestResult OpenVideoMixProjector(const Request &); + RequestResult OpenSourceProjector(const Request &); SessionPtr _session; static const std::unordered_map _handlerMap; diff --git a/src/requesthandler/RequestHandler_SceneItems.cpp b/src/requesthandler/RequestHandler_SceneItems.cpp index 236f1720..60b279a6 100644 --- a/src/requesthandler/RequestHandler_SceneItems.cpp +++ b/src/requesthandler/RequestHandler_SceneItems.cpp @@ -370,7 +370,7 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request) float finalWidth = scaleX * sourceWidth; 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."); + "The field `scaleX` is too small or large for the current source resolution."); sceneItemTransform.scale.x = scaleX; transformChanged = true; } @@ -381,7 +381,7 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request) float finalHeight = scaleY * sourceHeight; 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."); + "The field `scaleY` is too small or large for the current source resolution."); sceneItemTransform.scale.y = scaleY; transformChanged = true; } @@ -399,7 +399,7 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request) enum obs_bounds_type boundsType = r.RequestData["boundsType"]; if (boundsType == OBS_BOUNDS_NONE && r.RequestData["boundsType"] != "OBS_BOUNDS_NONE") return RequestResult::Error(RequestStatus::InvalidRequestField, - "The field boundsType has an invalid value."); + "The field `boundsType` has an invalid value."); sceneItemTransform.bounds_type = boundsType; transformChanged = true; } diff --git a/src/requesthandler/RequestHandler_Ui.cpp b/src/requesthandler/RequestHandler_Ui.cpp index 279663d7..fa1543c2 100644 --- a/src/requesthandler/RequestHandler_Ui.cpp +++ b/src/requesthandler/RequestHandler_Ui.cpp @@ -182,6 +182,7 @@ RequestResult RequestHandler::GetMonitorList(const Request &) nameAndIndex << screen->name().toStdString(); nameAndIndex << '(' << screenIndex << ')'; screenData["monitorName"] = nameAndIndex.str(); + screenData["monitorIndex"] = screenIndex; const QRect screenGeometry = screen->geometry(); screenData["monitorWidth"] = screenGeometry.width(); screenData["monitorHeight"] = screenGeometry.height(); @@ -192,3 +193,111 @@ RequestResult RequestHandler::GetMonitorList(const Request &) responseData["monitors"] = monitorsData; return RequestResult::Success(responseData); } + +/** + * Opens a projector for a specific output video mix. + * + * Mix types: + * - `OBS_WEBSOCKET_VIDEO_MIX_TYPE_PREVIEW` + * - `OBS_WEBSOCKET_VIDEO_MIX_TYPE_PROGRAM` + * - `OBS_WEBSOCKET_VIDEO_MIX_TYPE_MULTIVIEW` + * + * Note: This request serves to provide feature parity with 4.x. It is very likely to be changed/deprecated in a future release. + * + * @requestField videoMixType | String | Type of mix to open + * @requestField ?monitorIndex | Number | Monitor index, use `GetMonitorList` to obtain index | None | -1: Opens projector in windowed mode + * @requestField ?projectorGeometry | String | Size/Position data for a windowed projector, in Qt Base64 encoded format. Mutually exclusive with `monitorIndex` | N/A + * + * @requestType OpenVideoMixProjector + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @category ui + * @api requests + */ +RequestResult RequestHandler::OpenVideoMixProjector(const Request &request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + if (!request.ValidateString("mixType", statusCode, comment)) + return RequestResult::Error(statusCode, comment); + + std::string videoMixType = request.RequestData["videoMixType"]; + const char *projectorType; + if (videoMixType == "OBS_WEBSOCKET_VIDEO_MIX_TYPE_PREVIEW") + projectorType = "Preview"; + else if (videoMixType == "OBS_WEBSOCKET_VIDEO_MIX_TYPE_PROGRAM") + projectorType = "Program"; + else if (videoMixType == "OBS_WEBSOCKET_VIDEO_MIX_TYPE_MULTIVIEW") + projectorType = "Multiview"; + else + return RequestResult::Error(RequestStatus::InvalidRequestField, + "The field `videoMixType` has an invalid enum value."); + + int monitorIndex = -1; + if (request.Contains("monitorIndex")) { + if (!request.ValidateOptionalNumber("monitorIndex", statusCode, comment, -1, 9)) + return RequestResult::Error(statusCode, comment); + monitorIndex = request.RequestData["monitorIndex"]; + } + + std::string projectorGeometry; + if (request.Contains("projectorGeometry")) { + if (!request.ValidateOptionalString("projectorGeometry", statusCode, comment)) + return RequestResult::Error(statusCode, comment); + if (monitorIndex != -1) + return RequestResult::Error(RequestStatus::TooManyRequestFields, + "`monitorIndex` and `projectorGeometry` are mutually exclusive."); + projectorGeometry = request.RequestData["projectorGeometry"]; + } + + obs_frontend_open_projector(projectorType, monitorIndex, projectorGeometry.c_str(), nullptr); + + return RequestResult::Success(); +} + +/** + * Opens a projector for a source. + * + * Note: This request serves to provide feature parity with 4.x. It is very likely to be changed/deprecated in a future release. + * + * @requestField sourceName | String | Name of the source to open a projector for + * @requestField ?monitorIndex | Number | Monitor index, use `GetMonitorList` to obtain index | None | -1: Opens projector in windowed mode + * @requestField ?projectorGeometry | String | Size/Position data for a windowed projector, in Qt Base64 encoded format. Mutually exclusive with `monitorIndex` | N/A + * + * @requestType OpenSourceProjector + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @category ui + * @api requests + */ +RequestResult RequestHandler::OpenSourceProjector(const Request &request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); + if (!source) + return RequestResult::Error(statusCode, comment); + + int monitorIndex = -1; + if (request.Contains("monitorIndex")) { + if (!request.ValidateOptionalNumber("monitorIndex", statusCode, comment, -1, 9)) + return RequestResult::Error(statusCode, comment); + monitorIndex = request.RequestData["monitorIndex"]; + } + + std::string projectorGeometry; + if (request.Contains("projectorGeometry")) { + if (!request.ValidateOptionalString("projectorGeometry", statusCode, comment)) + return RequestResult::Error(statusCode, comment); + if (monitorIndex != -1) + return RequestResult::Error(RequestStatus::TooManyRequestFields, + "`monitorIndex` and `projectorGeometry` are mutually exclusive."); + projectorGeometry = request.RequestData["projectorGeometry"]; + } + + obs_frontend_open_projector("Source", monitorIndex, projectorGeometry.c_str(), obs_source_get_name(source)); + + return RequestResult::Success(); +} diff --git a/src/utils/Obs_ArrayHelper.cpp b/src/utils/Obs_ArrayHelper.cpp index 032d7d32..421f276b 100644 --- a/src/utils/Obs_ArrayHelper.cpp +++ b/src/utils/Obs_ArrayHelper.cpp @@ -60,14 +60,13 @@ std::vector Utils::Obs::ArrayHelper::GetHotkeyList() { std::vector ret; - auto cb = - [](void *data, obs_hotkey_id, obs_hotkey_t *hotkey) { - auto ret = static_cast *>(data); + auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *hotkey) { + auto ret = static_cast *>(data); - ret->push_back(hotkey); + ret->push_back(hotkey); - return true; - }; + return true; + }; obs_enum_hotkeys(cb, &ret); @@ -135,38 +134,37 @@ std::vector Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene, std::pair, bool> enumData; enumData.second = basic; - auto cb = - [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) { - auto enumData = static_cast, bool> *>(param); + auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) { + auto enumData = static_cast, bool> *>(param); - // TODO: Make ObjectHelper util for scene items + // TODO: Make ObjectHelper util for scene items - json item; - item["sceneItemId"] = obs_sceneitem_get_id(sceneItem); - item["sceneItemIndex"] = - enumData->first.size(); // Should be slightly faster than calling obs_sceneitem_get_order_position() - if (!enumData->second) { - item["sceneItemEnabled"] = obs_sceneitem_visible(sceneItem); - item["sceneItemLocked"] = obs_sceneitem_locked(sceneItem); - item["sceneItemTransform"] = ObjectHelper::GetSceneItemTransform(sceneItem); - item["sceneItemBlendMode"] = obs_sceneitem_get_blending_mode(sceneItem); - OBSSource itemSource = obs_sceneitem_get_source(sceneItem); - item["sourceName"] = obs_source_get_name(itemSource); - item["sourceType"] = obs_source_get_type(itemSource); - if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT) - item["inputKind"] = obs_source_get_id(itemSource); - else - item["inputKind"] = nullptr; - if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE) - item["isGroup"] = obs_source_is_group(itemSource); - else - item["isGroup"] = nullptr; - } + json item; + item["sceneItemId"] = obs_sceneitem_get_id(sceneItem); + item["sceneItemIndex"] = + enumData->first.size(); // Should be slightly faster than calling obs_sceneitem_get_order_position() + if (!enumData->second) { + item["sceneItemEnabled"] = obs_sceneitem_visible(sceneItem); + item["sceneItemLocked"] = obs_sceneitem_locked(sceneItem); + item["sceneItemTransform"] = ObjectHelper::GetSceneItemTransform(sceneItem); + item["sceneItemBlendMode"] = obs_sceneitem_get_blending_mode(sceneItem); + OBSSource itemSource = obs_sceneitem_get_source(sceneItem); + item["sourceName"] = obs_source_get_name(itemSource); + item["sourceType"] = obs_source_get_type(itemSource); + if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT) + item["inputKind"] = obs_source_get_id(itemSource); + else + item["inputKind"] = nullptr; + if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE) + item["isGroup"] = obs_source_is_group(itemSource); + else + item["isGroup"] = nullptr; + } - enumData->first.push_back(item); + enumData->first.push_back(item); - return true; - }; + return true; + }; obs_scene_enum_items(scene, cb, &enumData); diff --git a/src/utils/Obs_SearchHelper.cpp b/src/utils/Obs_SearchHelper.cpp index a8c43f27..7895f027 100644 --- a/src/utils/Obs_SearchHelper.cpp +++ b/src/utils/Obs_SearchHelper.cpp @@ -70,27 +70,26 @@ obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene enumData.name = name; enumData.offset = offset; - auto cb = - [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) { - auto enumData = static_cast(param); + auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) { + auto enumData = static_cast(param); - OBSSource itemSource = obs_sceneitem_get_source(sceneItem); - std::string sourceName = obs_source_get_name(itemSource); - if (sourceName == enumData->name) { - if (enumData->offset > 0) { - enumData->offset--; - } else { - if (enumData->ret) // Release existing selection in the case of last match selection - obs_sceneitem_release(enumData->ret); - obs_sceneitem_addref(sceneItem); - enumData->ret = sceneItem; - if (enumData->offset == 0) // Only break if in normal selection mode (not offset == -1) - return false; - } + OBSSource itemSource = obs_sceneitem_get_source(sceneItem); + std::string sourceName = obs_source_get_name(itemSource); + if (sourceName == enumData->name) { + if (enumData->offset > 0) { + enumData->offset--; + } else { + if (enumData->ret) // Release existing selection in the case of last match selection + obs_sceneitem_release(enumData->ret); + obs_sceneitem_addref(sceneItem); + enumData->ret = sceneItem; + if (enumData->offset == 0) // Only break if in normal selection mode (not offset == -1) + return false; } + } - return true; - }; + return true; + }; obs_scene_enum_items(scene, cb, &enumData); diff --git a/src/websocketserver/WebSocketServer_Protocol.cpp b/src/websocketserver/WebSocketServer_Protocol.cpp index 55c8aac5..2dec4d7e 100644 --- a/src/websocketserver/WebSocketServer_Protocol.cpp +++ b/src/websocketserver/WebSocketServer_Protocol.cpp @@ -302,7 +302,8 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces std::vector requestsVector; for (auto &requestJson : requests) { if (!requestJson["requestType"].is_string()) - requestJson["requestType"] = ""; // Workaround for what would otherwise be extensive additional logic for a rare edge case + requestJson["requestType"] = + ""; // Workaround for what would otherwise be extensive additional logic for a rare edge case std::string requestType = requestJson["requestType"]; json requestData = requestJson["requestData"]; json inputVariables = requestJson["inputVariables"];