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 <brendonovich@outlook.com>
This commit is contained in:
tt2468
2022-07-02 08:23:03 -07:00
parent aab925c0ed
commit 9cdfa41113
7 changed files with 167 additions and 56 deletions

View File

@ -181,6 +181,8 @@ const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_han
{"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog}, {"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog},
{"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog}, {"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog},
{"GetMonitorList", &RequestHandler::GetMonitorList}, {"GetMonitorList", &RequestHandler::GetMonitorList},
{"OpenVideoMixProjector", &RequestHandler::OpenVideoMixProjector},
{"OpenSourceProjector", &RequestHandler::OpenSourceProjector},
}; };
RequestHandler::RequestHandler(SessionPtr session) : _session(session) {} RequestHandler::RequestHandler(SessionPtr session) : _session(session) {}

View File

@ -200,6 +200,8 @@ private:
RequestResult OpenInputFiltersDialog(const Request &); RequestResult OpenInputFiltersDialog(const Request &);
RequestResult OpenInputInteractDialog(const Request &); RequestResult OpenInputInteractDialog(const Request &);
RequestResult GetMonitorList(const Request &); RequestResult GetMonitorList(const Request &);
RequestResult OpenVideoMixProjector(const Request &);
RequestResult OpenSourceProjector(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

@ -370,7 +370,7 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
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, 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; sceneItemTransform.scale.x = scaleX;
transformChanged = true; transformChanged = true;
} }
@ -381,7 +381,7 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
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, 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; sceneItemTransform.scale.y = scaleY;
transformChanged = true; transformChanged = true;
} }
@ -399,7 +399,7 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request &request)
enum obs_bounds_type boundsType = r.RequestData["boundsType"]; enum obs_bounds_type boundsType = r.RequestData["boundsType"];
if (boundsType == OBS_BOUNDS_NONE && r.RequestData["boundsType"] != "OBS_BOUNDS_NONE") if (boundsType == OBS_BOUNDS_NONE && r.RequestData["boundsType"] != "OBS_BOUNDS_NONE")
return RequestResult::Error(RequestStatus::InvalidRequestField, return RequestResult::Error(RequestStatus::InvalidRequestField,
"The field boundsType has an invalid value."); "The field `boundsType` has an invalid value.");
sceneItemTransform.bounds_type = boundsType; sceneItemTransform.bounds_type = boundsType;
transformChanged = true; transformChanged = true;
} }

View File

@ -182,6 +182,7 @@ RequestResult RequestHandler::GetMonitorList(const Request &)
nameAndIndex << screen->name().toStdString(); nameAndIndex << screen->name().toStdString();
nameAndIndex << '(' << screenIndex << ')'; nameAndIndex << '(' << screenIndex << ')';
screenData["monitorName"] = nameAndIndex.str(); screenData["monitorName"] = nameAndIndex.str();
screenData["monitorIndex"] = screenIndex;
const QRect screenGeometry = screen->geometry(); const QRect screenGeometry = screen->geometry();
screenData["monitorWidth"] = screenGeometry.width(); screenData["monitorWidth"] = screenGeometry.width();
screenData["monitorHeight"] = screenGeometry.height(); screenData["monitorHeight"] = screenGeometry.height();
@ -192,3 +193,111 @@ RequestResult RequestHandler::GetMonitorList(const Request &)
responseData["monitors"] = monitorsData; responseData["monitors"] = monitorsData;
return RequestResult::Success(responseData); 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();
}

View File

@ -60,8 +60,7 @@ std::vector<obs_hotkey_t *> Utils::Obs::ArrayHelper::GetHotkeyList()
{ {
std::vector<obs_hotkey_t *> ret; std::vector<obs_hotkey_t *> ret;
auto cb = auto cb = [](void *data, obs_hotkey_id, obs_hotkey_t *hotkey) {
[](void *data, obs_hotkey_id, obs_hotkey_t *hotkey) {
auto ret = static_cast<std::vector<obs_hotkey_t *> *>(data); auto ret = static_cast<std::vector<obs_hotkey_t *> *>(data);
ret->push_back(hotkey); ret->push_back(hotkey);
@ -135,8 +134,7 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene,
std::pair<std::vector<json>, bool> enumData; std::pair<std::vector<json>, bool> enumData;
enumData.second = basic; enumData.second = basic;
auto cb = auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
[](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
auto enumData = static_cast<std::pair<std::vector<json>, bool> *>(param); auto enumData = static_cast<std::pair<std::vector<json>, bool> *>(param);
// TODO: Make ObjectHelper util for scene items // TODO: Make ObjectHelper util for scene items

View File

@ -70,8 +70,7 @@ obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene
enumData.name = name; enumData.name = name;
enumData.offset = offset; enumData.offset = offset;
auto cb = auto cb = [](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
[](obs_scene_t *, obs_sceneitem_t *sceneItem, void *param) {
auto enumData = static_cast<SceneItemSearchData *>(param); auto enumData = static_cast<SceneItemSearchData *>(param);
OBSSource itemSource = obs_sceneitem_get_source(sceneItem); OBSSource itemSource = obs_sceneitem_get_source(sceneItem);

View File

@ -302,7 +302,8 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
std::vector<RequestBatchRequest> requestsVector; std::vector<RequestBatchRequest> requestsVector;
for (auto &requestJson : requests) { for (auto &requestJson : requests) {
if (!requestJson["requestType"].is_string()) 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"]; std::string requestType = requestJson["requestType"];
json requestData = requestJson["requestData"]; json requestData = requestJson["requestData"];
json inputVariables = requestJson["inputVariables"]; json inputVariables = requestJson["inputVariables"];