obs-websocket/src/requesthandler/RequestHandler_General.cpp
tt2468 fcbe11616d
docs: Overhaul documentation (#863)
More docs-related commits will follow, but this needs to be merged in order to continue with other development.

* Docs: Overhaul docs generator (beginning)

* docs: Rename comments file

* docs: Move comments gitignore

* docs: Initial request documentation

* docs: Improvements to comment processing

* docs: More improvements

* docs: Add enum functionality for protocol.json

* WebSocketServer: Document enums

* RequestHandler: Document RequestStatus enum

* Base: Move ObsWebSocketRequestBatchExecutionType to its own file

Moves it to its own file, renaming it to `RequestBatchExecutionType`.
Changes the RPC to use integer values for selecting execution type
instead of strings.

* docs: Update introduction header

Removes the enum section, and documents RequestBatchExecutionType.

* WebSocketCloseCode: Shuffle a bit

* Base: Use `field` instead of `key` or `parameter` in most places

* RequestStatus: Mild shuffle

It was really bothering me that OutputPaused and OutputNotPaused
had to be separated, so we're breaking it while we're breaking
other stuff.

* docs: Delete old files

They may be added back in some form, but for now I'm getting them
out of the way.

* docs: Add enum identifier value

Forgot to add this before, oops

* docs: Document more enums

* docs: Add basic protocol.md generator

* docs: More work on MD generator

* docs: MD generator should be finished now

* docs: More fixes

* docs: More fixes

* docs: More tweaks + add readme

* docs: Update readme and add inputs docs

* docs: More documentation
2021-12-10 21:38:18 -08:00

315 lines
12 KiB
C++

/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <QImageWriter>
#include "RequestHandler.h"
#include "../websocketserver/WebSocketServer.h"
#include "../eventhandler/types/EventSubscription.h"
#include "../obs-websocket.h"
/**
* Gets data about the current plugin and RPC version.
*
* @responseField obsVersion | String | Current OBS Studio version
* @responseField obsWebSocketVersion | String | Current obs-websocket version
* @responseField rpcVersion | Number | Current latest obs-websocket RPC version
* @responseField availableRequests | Array<String> | Array of available RPC requests for the currently negotiated RPC version
*
* @requestType GetVersion
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::GetVersion(const Request&)
{
json responseData;
responseData["obsVersion"] = Utils::Obs::StringHelper::GetObsVersion();
responseData["obsWebSocketVersion"] = OBS_WEBSOCKET_VERSION;
responseData["rpcVersion"] = OBS_WEBSOCKET_RPC_VERSION;
responseData["availableRequests"] = GetRequestList();
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
std::vector<std::string> supportedImageFormats;
for (const QByteArray& format : imageWriterFormats) {
supportedImageFormats.push_back(format.toStdString());
}
responseData["supportedImageFormats"] = supportedImageFormats;
return RequestResult::Success(responseData);
}
/**
* Broadcasts a `CustomEvent` to all WebSocket clients. Receivers are clients which are identified and subscribed.
*
* @requestField eventData | Object | Data payload to emit to all receivers
*
* @requestType BroadcastCustomEvent
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::BroadcastCustomEvent(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateObject("eventData", statusCode, comment))
return RequestResult::Error(statusCode, comment);
auto webSocketServer = GetWebSocketServer();
if (!webSocketServer)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Unable to send event.");
webSocketServer->BroadcastEvent(EventSubscription::General, "CustomEvent", request.RequestData["eventData"]);
return RequestResult::Success();
}
/**
* Gets statistics about OBS, obs-websocket, and the current session.
*
* @responseField cpuUsage | Number | Current CPU usage in percent
* @responseField memoryUsage | Number | Amount of memory in MB currently being used by OBS
* @responseField availableDiskSpace | Number | Available disk space on the device being used for recording storage
* @responseField activeFps | Number | Current FPS being rendered
* @responseField averageFrameRenderTime | Number | Average time in milliseconds that OBS is taking to render a frame
* @responseField renderSkippedFrames | Number | Number of frames skipped by OBS in the render thread
* @responseField renderTotalFrames | Number | Total number of frames outputted by the render thread
* @responseField outputSkippedFrames | Number | Number of frames skipped by OBS in the output thread
* @responseField outputTotalFrames | Number | Total number of frames outputted by the output thread
* @responseField webSocketSessionIncomingMessages | Number | Total number of messages received by obs-websocket from the client
* @responseField webSocketSessionOutgoingMessages | Number | Total number of messages sent by obs-websocket to the client
*
* @requestType GetStats
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::GetStats(const Request&)
{
json responseData = Utils::Obs::DataHelper::GetStats();
responseData["webSocketSessionIncomingMessages"] = _session->IncomingMessages();
responseData["webSocketSessionOutgoingMessages"] = _session->OutgoingMessages();
return RequestResult::Success(responseData);
}
/**
* Gets an array of all hotkey names in OBS
*
* @responseField hotkeys | Array<String> | Array of hotkey names
*
* @requestType GetHotkeyList
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::GetHotkeyList(const Request&)
{
json responseData;
responseData["hotkeys"] = Utils::Obs::ListHelper::GetHotkeyNameList();
return RequestResult::Success(responseData);
}
/**
* Triggers a hotkey using its name. See `GetHotkeyList`
*
* @requestField hotkeyName | String | Name of the hotkey to trigger
*
* @requestType TriggerHotkeyByName
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::TriggerHotkeyByName(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateString("hotkeyName", statusCode, comment))
return RequestResult::Error(statusCode, comment);
obs_hotkey_t *hotkey = Utils::Obs::SearchHelper::GetHotkeyByName(request.RequestData["hotkeyName"]);
if (!hotkey)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No hotkeys were found by that name.");
obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hotkey), true);
return RequestResult::Success();
}
/**
* Triggers a hotkey using a sequence of keys.
*
* @requestField ?keyId | String | The OBS key ID to use. See https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h | Not pressed
* @requestField ?keyModifiers | Object | Object containing key modifiers to apply | Ignored
* @requestField ?keyModifiers.shift | Boolean | Press Shift | Not pressed
* @requestField ?keyModifiers.control | Boolean | Press CTRL | Not pressed
* @requestField ?keyModifiers.alt | Boolean | Press ALT | Not pressed
* @requestField ?keyModifiers.command | Boolean | Press CMD (Mac) | Not pressed
*
* @requestType TriggerHotkeyByKeySequence
* @complexity 4
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
{
obs_key_combination_t combo = {0};
RequestStatus::RequestStatus statusCode = RequestStatus::NoError;
std::string comment;
if (request.Contains("keyId")) {
if (!request.ValidateOptionalString("keyId", statusCode, comment))
return RequestResult::Error(statusCode, comment);
std::string keyId = request.RequestData["keyId"];
combo.key = obs_key_from_name(keyId.c_str());
}
statusCode = RequestStatus::NoError;
if (request.Contains("keyModifiers")) {
if (!request.ValidateOptionalObject("keyModifiers", statusCode, comment, true))
return RequestResult::Error(statusCode, comment);
const json keyModifiersJson = request.RequestData["keyModifiers"];
uint32_t keyModifiers = 0;
if (keyModifiersJson.contains("shift") && keyModifiersJson["shift"].is_boolean() && keyModifiersJson["shift"].get<bool>())
keyModifiers |= INTERACT_SHIFT_KEY;
if (keyModifiersJson.contains("control") && keyModifiersJson["control"].is_boolean() && keyModifiersJson["control"].get<bool>())
keyModifiers |= INTERACT_CONTROL_KEY;
if (keyModifiersJson.contains("alt") && keyModifiersJson["alt"].is_boolean() && keyModifiersJson["alt"].get<bool>())
keyModifiers |= INTERACT_ALT_KEY;
if (keyModifiersJson.contains("command") && keyModifiersJson["command"].is_boolean() && keyModifiersJson["command"].get<bool>())
keyModifiers |= INTERACT_COMMAND_KEY;
combo.modifiers = keyModifiers;
}
if (!combo.modifiers && (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE))
return RequestResult::Error(RequestStatus::CannotAct, "Your provided request fields cannot be used to trigger a hotkey.");
// Apparently things break when you don't start by setting the combo to false
obs_hotkey_inject_event(combo, false);
obs_hotkey_inject_event(combo, true);
obs_hotkey_inject_event(combo, false);
return RequestResult::Success();
}
/**
* Gets whether studio is enabled.
*
* @responseField studioModeEnabled | Boolean | Whether studio mode is enabled
*
* @requestType GetStudioModeEnabled
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::GetStudioModeEnabled(const Request&)
{
json responseData;
responseData["studioModeEnabled"] = obs_frontend_preview_program_mode_active();
return RequestResult::Success(responseData);
}
/**
* Enables or disables studio mode
*
* @requestField studioModeEnabled | Boolean | True == Enabled, False == Disabled
*
* @requestType SetStudioModeEnabled
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateBoolean("studioModeEnabled", statusCode, comment))
return RequestResult::Error(statusCode, comment);
// Avoid queueing tasks if nothing will change
if (obs_frontend_preview_program_mode_active() != request.RequestData["studioModeEnabled"]) {
// (Bad) Create a boolean then pass it as a reference to the task. Requires `wait` in obs_queue_task() to be true, else undefined behavior
bool studioModeEnabled = request.RequestData["studioModeEnabled"];
// Queue the task inside of the UI thread to prevent race conditions
obs_queue_task(OBS_TASK_UI, [](void* param) {
auto studioModeEnabled = (bool*)param;
obs_frontend_set_preview_program_mode(*studioModeEnabled);
}, &studioModeEnabled, true);
}
return RequestResult::Success();
}
/**
* Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`.
*
* @requestField sleepMillis | Number | Number of milliseconds to sleep for (if `SERIAL_REALTIME` mode) | >= 0, <= 50000
* @requestField sleepFrames | Number | Number of frames to sleep for (if `SERIAL_FRAME` mode) | >= 0, <= 10000
*
* @requestType Sleep
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::Sleep(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (request.ExecutionType == RequestBatchExecutionType::SerialRealtime) {
if (!request.ValidateNumber("sleepMillis", statusCode, comment, 0, 50000))
return RequestResult::Error(statusCode, comment);
int64_t sleepMillis = request.RequestData["sleepMillis"];
std::this_thread::sleep_for(std::chrono::milliseconds(sleepMillis));
return RequestResult::Success();
} else if (request.ExecutionType == RequestBatchExecutionType::SerialFrame) {
if (!request.ValidateNumber("sleepFrames", statusCode, comment, 0, 10000))
return RequestResult::Error(statusCode, comment);
RequestResult ret = RequestResult::Success();
ret.SleepFrames = request.RequestData["sleepFrames"];
return ret;
} else {
return RequestResult::Error(RequestStatus::UnsupportedRequestBatchExecutionType);
}
}