diff --git a/docs/README.md b/docs/README.md index d3640b5f..dc16f9bb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,12 +15,51 @@ Some notes about documenting: - The description can be multiple lines, but must be contained above the first documentation property (the lines starting with `@`). - Value types are in reference to JSON values. The only ones you should use are `Any`, `String`, `Boolean`, `Number`, `Array`, `Object`. - `Array` types follow this format: `Array`, for example `Array` to specify an array of strings. + - `Object` types can be replaced with custom type names defined using `@typedef` (see "Creating type documentation" below). + - When referencing a custom type, use the type name directly (e.g., `SceneItemTransform`) or in an array (e.g., `Array`). Formatting notes: - Fields should have their columns aligned. So in a request, the columns of all `@requestField`s should be aligned. - We suggest looking at how other enums/events/requests have been documented before documenting one of your own, to get a general feel of how things have been formatted. +## Creating type documentation + +Custom types allow you to define reusable complex object structures instead of using generic `Object` types. This enables client libraries to auto-generate proper typed models. + +Types follow this code comment format: + +```js +/** + * [description] + * + * @typedef [type name] + * @field [field name] | [value type] | [field description] + * [... more @field entries ...] + * @api types + */ +``` + +Example code comment: + +```js +/** + * Scene item transform information. + * + * @typedef SceneItemTransform + * @field sourceWidth | Number | Width of the source associated with the scene item + * @field sourceHeight | Number | Height of the source associated with the scene item + * @field positionX | Number | The x position of the scene item from the left + * @field positionY | Number | The y position of the scene item from the top + * @field rotation | Number | The clockwise rotation of the scene item in degrees around the point of alignment + * @api types + */ +``` + +- Custom types are automatically added to the `types` section in `protocol.json` and `protocol.md`. +- You can reference custom types in `@requestField`, `@responseField`, and `@dataField` declarations. +- Types can reference other types (e.g., a `SceneItem` type can have a field of type `SceneItemTransform`). + ## Creating enum documentation Enums follow this code comment format: diff --git a/docs/docs/generate_md.py b/docs/docs/generate_md.py index ee746281..cf76a50b 100644 --- a/docs/docs/generate_md.py +++ b/docs/docs/generate_md.py @@ -1,312 +1,357 @@ -import logging -logging.basicConfig(level=logging.INFO, format="%(asctime)s [generate_md.py] [%(levelname)s] %(message)s") -import os -import sys -import json - -enumTypeOrder = [ - 'WebSocketOpCode', - 'WebSocketCloseCode', - 'RequestBatchExecutionType', - 'RequestStatus', - 'EventSubscription', - 'ObsMediaInputAction', - 'ObsOutputState' -] - -categoryOrder = [ - 'General', - 'Config', - 'Sources', - 'Scenes', - 'Inputs', - 'Transitions', - 'Filters', - 'Scene Items', - 'Outputs', - 'Stream', - 'Record', - 'Media Inputs', - 'Ui', - 'High-Volume' -] - -requestFieldHeader = """ -**Request Fields:** - -| Name | Type | Description | Value Restrictions | ?Default Behavior | -| ---- | :---: | ----------- | :----------------: | ----------------- | -""" - -responseFieldHeader = """ -**Response Fields:** - -| Name | Type | Description | -| ---- | :---: | ----------- | -""" - -dataFieldHeader = """ -**Data Fields:** - -| Name | Type | Description | -| ---- | :---: | ----------- | -""" - -fragments = [] - -# Utils -####################################################################################################################### - -def read_file(fileName): - with open(fileName, 'r') as f: - return f.read() - -def get_fragment(name, register = True): - global fragments - testFragmentName = name.replace(' ', '-').replace(':', '').lower() - if testFragmentName in fragments: - testFragmentName += '-1' - increment = 1 - while testFragmentName in fragments: - increment += 1 - testFragmentName[:-1] = str(increment) - if register: - fragments.append(testFragmentName) - return testFragmentName - -def get_category_items(items, category): - ret = [] - for item in requests: - if item['category'] != category: - continue - ret.append(item) - return ret - -def get_enums_toc(enums): - ret = '' - for enumType in enumTypeOrder: - enum = None - for enumIt in enums: - if enumIt['enumType'] == enumType: - enum = enumIt - break - if not enum: - continue - typeFragment = get_fragment(enumType, False) - ret += '- [{}](#{})\n'.format(enumType, typeFragment) - for enumIdentifier in enum['enumIdentifiers']: - enumIdentifier = enumIdentifier['enumIdentifier'] - enumIdentifierHeader = '{}::{}'.format(enumType, enumIdentifier) - enumIdentifierFragment = get_fragment(enumIdentifierHeader, False) - ret += ' - [{}](#{})\n'.format(enumIdentifierHeader, enumIdentifierFragment) - return ret - -def get_enums(enums): - ret = '' - for enumType in enumTypeOrder: - enum = None - for enumIt in enums: - if enumIt['enumType'] == enumType: - enum = enumIt - break - if not enum: - continue - typeFragment = get_fragment(enumType) - ret += '## {}\n\n'.format(enumType) - for enumIdentifier in enum['enumIdentifiers']: - enumIdentifierString = enumIdentifier['enumIdentifier'] - enumIdentifierHeader = '{}::{}'.format(enumType, enumIdentifierString) - enumIdentifierFragment = get_fragment(enumIdentifierHeader, False) - ret += '### {}\n\n'.format(enumIdentifierHeader) - ret += '{}\n\n'.format(enumIdentifier['description']) - ret += '- Identifier Value: `{}`\n'.format(enumIdentifier['enumValue']) - ret += '- Latest Supported RPC Version: `{}`\n'.format(enumIdentifier['rpcVersion']) - if enumIdentifier['deprecated']: - ret += '- **⚠️ Deprecated. ⚠️**\n' - if enumIdentifier['initialVersion'].lower() == 'unreleased': - ret += '- Unreleased\n' - else: - ret += '- Added in v{}\n'.format(enumIdentifier['initialVersion']) - if enumIdentifier != enum['enumIdentifiers'][-1]: - ret += '\n---\n\n' - else: - ret += '\n' - return ret - -def get_requests_toc(requests): - ret = '' - for category in categoryOrder: - requestsOut = [] - for request in requests: - if request['category'] != category.lower(): - continue - requestsOut.append(request) - if not len(requestsOut): - continue - categoryFragment = get_fragment(category, False) - ret += '- [{} Requests](#{}-requests)\n'.format(category, categoryFragment) - for request in requestsOut: - requestType = request['requestType'] - requestTypeFragment = get_fragment(requestType, False) - ret += ' - [{}](#{})\n'.format(requestType, requestTypeFragment) - return ret - -def get_requests(requests): - ret = '' - for category in categoryOrder: - requestsOut = [] - for request in requests: - if request['category'] != category.lower(): - continue - requestsOut.append(request) - if not len(requestsOut): - continue - categoryFragment = get_fragment(category) - ret += '## {} Requests\n\n'.format(category) - for request in requestsOut: - requestType = request['requestType'] - requestTypeFragment = get_fragment(requestType) - ret += '### {}\n\n'.format(requestType) - ret += '{}\n\n'.format(request['description']) - ret += '- Complexity Rating: `{}/5`\n'.format(request['complexity']) - ret += '- Latest Supported RPC Version: `{}`\n'.format(request['rpcVersion']) - if request['deprecated']: - ret += '- **⚠️ Deprecated. ⚠️**\n' - if request['initialVersion'].lower() == 'unreleased': - ret += '- Unreleased\n' - else: - ret += '- Added in v{}\n'.format(request['initialVersion']) - - if request['requestFields']: - ret += requestFieldHeader - for requestField in request['requestFields']: - valueType = requestField['valueType'].replace('<', "<").replace('>', ">") - valueRestrictions = requestField['valueRestrictions'] if requestField['valueRestrictions'] else 'None' - valueOptional = '?' if requestField['valueOptional'] else '' - valueOptionalBehavior = requestField['valueOptionalBehavior'] if requestField['valueOptional'] and requestField['valueOptionalBehavior'] else 'N/A' - ret += '| {}{} | {} | {} | {} | {} |\n'.format(valueOptional, requestField['valueName'], valueType, requestField['valueDescription'], valueRestrictions, valueOptionalBehavior) - - if request['responseFields']: - ret += responseFieldHeader - for responseField in request['responseFields']: - valueType = responseField['valueType'].replace('<', "<").replace('>', ">") - ret += '| {} | {} | {} |\n'.format(responseField['valueName'], valueType, responseField['valueDescription']) - - if request != requestsOut[-1]: - ret += '\n---\n\n' - else: - ret += '\n' - return ret - -def get_events_toc(events): - ret = '' - for category in categoryOrder: - eventsOut = [] - for event in events: - if event['category'] != category.lower(): - continue - eventsOut.append(event) - if not len(eventsOut): - continue - categoryFragment = get_fragment(category, False) - ret += '- [{} Events](#{}-events)\n'.format(category, categoryFragment) - for event in eventsOut: - eventType = event['eventType'] - eventTypeFragment = get_fragment(eventType, False) - ret += ' - [{}](#{})\n'.format(eventType, eventTypeFragment) - return ret - -def get_events(events): - ret = '' - for category in categoryOrder: - eventsOut = [] - for event in events: - if event['category'] != category.lower(): - continue - eventsOut.append(event) - if not len(eventsOut): - continue - categoryFragment = get_fragment(category) - ret += '## {} Events\n\n'.format(category) - for event in eventsOut: - eventType = event['eventType'] - eventTypeFragment = get_fragment(eventType) - ret += '### {}\n\n'.format(eventType) - ret += '{}\n\n'.format(event['description']) - ret += '- Complexity Rating: `{}/5`\n'.format(event['complexity']) - ret += '- Latest Supported RPC Version: `{}`\n'.format(event['rpcVersion']) - if event['deprecated']: - ret += '- **⚠️ Deprecated. ⚠️**\n' - if event['initialVersion'].lower() == 'unreleased': - ret += '- Unreleased\n' - else: - ret += '- Added in v{}\n'.format(event['initialVersion']) - - if event['dataFields']: - ret += dataFieldHeader - for dataField in event['dataFields']: - valueType = dataField['valueType'].replace('<', "<").replace('>', ">") - ret += '| {} | {} | {} |\n'.format(dataField['valueName'], valueType, dataField['valueDescription']) - - if event != eventsOut[-1]: - ret += '\n---\n\n' - else: - ret += '\n' - return ret - -# Actual code -####################################################################################################################### - -# Read versions json -try: - with open('../versions.json', 'r') as f: - versions = json.load(f) -except IOError: - logging.error('Failed to get global versions. Versions file not configured?') - os.exit(1) - -# Read protocol json -with open('../generated/protocol.json', 'r') as f: - protocol = json.load(f) - -output = "\n" -output += "\n" - -# Insert introduction partial -output += read_file('partials/introduction.md') -logging.info('Inserted introduction section.') - -output += '\n' - -# Generate enums MD -output += read_file('partials/enumsHeader.md') -output += '\n' -output += get_enums_toc(protocol['enums']) -output += '\n' -output += get_enums(protocol['enums']) -logging.info('Inserted enums section.') - -# Generate events MD -output += read_file('partials/eventsHeader.md') -output += '\n' -output += get_events_toc(protocol['events']) -output += '\n' -output += get_events(protocol['events']) -logging.info('Inserted events section.') - -# Generate requests MD -output += read_file('partials/requestsHeader.md') -output += '\n' -output += get_requests_toc(protocol['requests']) -output += '\n' -output += get_requests(protocol['requests']) -logging.info('Inserted requests section.') - -if output.endswith('\n\n'): - output = output[:-1] - -# Write new protocol MD -with open('../generated/protocol.md', 'w') as f: - f.write(output) - -logging.info('Finished generating protocol.md.') +import logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s [generate_md.py] [%(levelname)s] %(message)s") +import os +import sys +import json + +enumTypeOrder = [ + 'WebSocketOpCode', + 'WebSocketCloseCode', + 'RequestBatchExecutionType', + 'RequestStatus', + 'EventSubscription', + 'ObsMediaInputAction', + 'ObsOutputState' +] + +categoryOrder = [ + 'General', + 'Config', + 'Sources', + 'Scenes', + 'Inputs', + 'Transitions', + 'Filters', + 'Scene Items', + 'Outputs', + 'Stream', + 'Record', + 'Media Inputs', + 'Ui', + 'High-Volume' +] + +requestFieldHeader = """ +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +""" + +responseFieldHeader = """ +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +""" + +dataFieldHeader = """ +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +""" + +typeFieldHeader = """ +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +""" + +fragments = [] + +# Utils +####################################################################################################################### + +def read_file(fileName): + with open(fileName, 'r') as f: + return f.read() + +def get_fragment(name, register = True): + global fragments + testFragmentName = name.replace(' ', '-').replace(':', '').lower() + if testFragmentName in fragments: + testFragmentName += '-1' + increment = 1 + while testFragmentName in fragments: + increment += 1 + testFragmentName[:-1] = str(increment) + if register: + fragments.append(testFragmentName) + return testFragmentName + +def get_category_items(items, category): + ret = [] + for item in requests: + if item['category'] != category: + continue + ret.append(item) + return ret + +def get_enums_toc(enums): + ret = '' + for enumType in enumTypeOrder: + enum = None + for enumIt in enums: + if enumIt['enumType'] == enumType: + enum = enumIt + break + if not enum: + continue + typeFragment = get_fragment(enumType, False) + ret += '- [{}](#{})\n'.format(enumType, typeFragment) + for enumIdentifier in enum['enumIdentifiers']: + enumIdentifier = enumIdentifier['enumIdentifier'] + enumIdentifierHeader = '{}::{}'.format(enumType, enumIdentifier) + enumIdentifierFragment = get_fragment(enumIdentifierHeader, False) + ret += ' - [{}](#{})\n'.format(enumIdentifierHeader, enumIdentifierFragment) + return ret + +def get_types_toc(types): + ret = '' + for typedef in types: + typeName = typedef['typeName'] + typeFragment = get_fragment(typeName, False) + ret += '- [{}](#{})\n'.format(typeName, typeFragment) + return ret + +def get_types(types): + ret = '' + for typedef in types: + typeName = typedef['typeName'] + typeFragment = get_fragment(typeName) + ret += '## {}\n\n'.format(typeName) + ret += '{}\n\n'.format(typedef['description']) + + if typedef['typeFields']: + ret += typeFieldHeader + for typeField in typedef['typeFields']: + fieldType = typeField['fieldType'].replace('<', "<").replace('>', ">") + ret += '| {} | {} | {} |\n'.format(typeField['fieldName'], fieldType, typeField['fieldDescription']) + + if typedef != types[-1]: + ret += '\n---\n\n' + else: + ret += '\n' + return ret + +def get_enums(enums): + ret = '' + for enumType in enumTypeOrder: + enum = None + for enumIt in enums: + if enumIt['enumType'] == enumType: + enum = enumIt + break + if not enum: + continue + typeFragment = get_fragment(enumType) + ret += '## {}\n\n'.format(enumType) + for enumIdentifier in enum['enumIdentifiers']: + enumIdentifierString = enumIdentifier['enumIdentifier'] + enumIdentifierHeader = '{}::{}'.format(enumType, enumIdentifierString) + enumIdentifierFragment = get_fragment(enumIdentifierHeader, False) + ret += '### {}\n\n'.format(enumIdentifierHeader) + ret += '{}\n\n'.format(enumIdentifier['description']) + ret += '- Identifier Value: `{}`\n'.format(enumIdentifier['enumValue']) + ret += '- Latest Supported RPC Version: `{}`\n'.format(enumIdentifier['rpcVersion']) + if enumIdentifier['deprecated']: + ret += '- **⚠️ Deprecated. ⚠️**\n' + if enumIdentifier['initialVersion'].lower() == 'unreleased': + ret += '- Unreleased\n' + else: + ret += '- Added in v{}\n'.format(enumIdentifier['initialVersion']) + if enumIdentifier != enum['enumIdentifiers'][-1]: + ret += '\n---\n\n' + else: + ret += '\n' + return ret + +def get_requests_toc(requests): + ret = '' + for category in categoryOrder: + requestsOut = [] + for request in requests: + if request['category'] != category.lower(): + continue + requestsOut.append(request) + if not len(requestsOut): + continue + categoryFragment = get_fragment(category, False) + ret += '- [{} Requests](#{}-requests)\n'.format(category, categoryFragment) + for request in requestsOut: + requestType = request['requestType'] + requestTypeFragment = get_fragment(requestType, False) + ret += ' - [{}](#{})\n'.format(requestType, requestTypeFragment) + return ret + +def get_requests(requests): + ret = '' + for category in categoryOrder: + requestsOut = [] + for request in requests: + if request['category'] != category.lower(): + continue + requestsOut.append(request) + if not len(requestsOut): + continue + categoryFragment = get_fragment(category) + ret += '## {} Requests\n\n'.format(category) + for request in requestsOut: + requestType = request['requestType'] + requestTypeFragment = get_fragment(requestType) + ret += '### {}\n\n'.format(requestType) + ret += '{}\n\n'.format(request['description']) + ret += '- Complexity Rating: `{}/5`\n'.format(request['complexity']) + ret += '- Latest Supported RPC Version: `{}`\n'.format(request['rpcVersion']) + if request['deprecated']: + ret += '- **⚠️ Deprecated. ⚠️**\n' + if request['initialVersion'].lower() == 'unreleased': + ret += '- Unreleased\n' + else: + ret += '- Added in v{}\n'.format(request['initialVersion']) + + if request['requestFields']: + ret += requestFieldHeader + for requestField in request['requestFields']: + valueType = requestField['valueType'].replace('<', "<").replace('>', ">") + valueRestrictions = requestField['valueRestrictions'] if requestField['valueRestrictions'] else 'None' + valueOptional = '?' if requestField['valueOptional'] else '' + valueOptionalBehavior = requestField['valueOptionalBehavior'] if requestField['valueOptional'] and requestField['valueOptionalBehavior'] else 'N/A' + ret += '| {}{} | {} | {} | {} | {} |\n'.format(valueOptional, requestField['valueName'], valueType, requestField['valueDescription'], valueRestrictions, valueOptionalBehavior) + + if request['responseFields']: + ret += responseFieldHeader + for responseField in request['responseFields']: + valueType = responseField['valueType'].replace('<', "<").replace('>', ">") + ret += '| {} | {} | {} |\n'.format(responseField['valueName'], valueType, responseField['valueDescription']) + + if request != requestsOut[-1]: + ret += '\n---\n\n' + else: + ret += '\n' + return ret + +def get_events_toc(events): + ret = '' + for category in categoryOrder: + eventsOut = [] + for event in events: + if event['category'] != category.lower(): + continue + eventsOut.append(event) + if not len(eventsOut): + continue + categoryFragment = get_fragment(category, False) + ret += '- [{} Events](#{}-events)\n'.format(category, categoryFragment) + for event in eventsOut: + eventType = event['eventType'] + eventTypeFragment = get_fragment(eventType, False) + ret += ' - [{}](#{})\n'.format(eventType, eventTypeFragment) + return ret + +def get_events(events): + ret = '' + for category in categoryOrder: + eventsOut = [] + for event in events: + if event['category'] != category.lower(): + continue + eventsOut.append(event) + if not len(eventsOut): + continue + categoryFragment = get_fragment(category) + ret += '## {} Events\n\n'.format(category) + for event in eventsOut: + eventType = event['eventType'] + eventTypeFragment = get_fragment(eventType) + ret += '### {}\n\n'.format(eventType) + ret += '{}\n\n'.format(event['description']) + ret += '- Complexity Rating: `{}/5`\n'.format(event['complexity']) + ret += '- Latest Supported RPC Version: `{}`\n'.format(event['rpcVersion']) + if event['deprecated']: + ret += '- **⚠️ Deprecated. ⚠️**\n' + if event['initialVersion'].lower() == 'unreleased': + ret += '- Unreleased\n' + else: + ret += '- Added in v{}\n'.format(event['initialVersion']) + + if event['dataFields']: + ret += dataFieldHeader + for dataField in event['dataFields']: + valueType = dataField['valueType'].replace('<', "<").replace('>', ">") + ret += '| {} | {} | {} |\n'.format(dataField['valueName'], valueType, dataField['valueDescription']) + + if event != eventsOut[-1]: + ret += '\n---\n\n' + else: + ret += '\n' + return ret + +# Actual code +####################################################################################################################### + +# Read versions json +try: + with open('../versions.json', 'r') as f: + versions = json.load(f) +except IOError: + logging.error('Failed to get global versions. Versions file not configured?') + os.exit(1) + +# Read protocol json +with open('../generated/protocol.json', 'r') as f: + protocol = json.load(f) + +output = "\n" +output += "\n" + +# Insert introduction partial +output += read_file('partials/introduction.md') +logging.info('Inserted introduction section.') + +output += '\n' + +# Generate types MD (if types exist) +if 'types' in protocol and protocol['types']: + output += '# Types\n\n' + output += 'The following types are used as complex object structures in various requests and events.\n\n' + output += '## Table of Contents\n\n' + output += get_types_toc(protocol['types']) + output += '\n' + output += get_types(protocol['types']) + logging.info('Inserted types section.') + +# Generate enums MD +output += read_file('partials/enumsHeader.md') +output += '\n' +output += get_enums_toc(protocol['enums']) +output += '\n' +output += get_enums(protocol['enums']) +logging.info('Inserted enums section.') + +# Generate events MD +output += read_file('partials/eventsHeader.md') +output += '\n' +output += get_events_toc(protocol['events']) +output += '\n' +output += get_events(protocol['events']) +logging.info('Inserted events section.') + +# Generate requests MD +output += read_file('partials/requestsHeader.md') +output += '\n' +output += get_requests_toc(protocol['requests']) +output += '\n' +output += get_requests(protocol['requests']) +logging.info('Inserted requests section.') + +if output.endswith('\n\n'): + output = output[:-1] + +# Write new protocol MD +with open('../generated/protocol.md', 'w') as f: + f.write(output) + +logging.info('Finished generating protocol.md.') diff --git a/docs/docs/partials/enumsHeader.md b/docs/docs/partials/enumsHeader.md index 118c81d1..c400c6c6 100644 --- a/docs/docs/partials/enumsHeader.md +++ b/docs/docs/partials/enumsHeader.md @@ -1,5 +1,5 @@ -# Enums - -These are enumeration declarations, which are referenced throughout obs-websocket's protocol. - -## Enumerations Table of Contents +# Enums + +These are enumeration declarations, which are referenced throughout obs-websocket's protocol. + +## Enumerations Table of Contents diff --git a/docs/docs/partials/eventsHeader.md b/docs/docs/partials/eventsHeader.md index 36150ca0..a32494ff 100644 --- a/docs/docs/partials/eventsHeader.md +++ b/docs/docs/partials/eventsHeader.md @@ -1,3 +1,3 @@ -# Events - -## Events Table of Contents +# Events + +## Events Table of Contents diff --git a/docs/docs/partials/introduction.md b/docs/docs/partials/introduction.md index 318600df..171d9324 100644 --- a/docs/docs/partials/introduction.md +++ b/docs/docs/partials/introduction.md @@ -1,432 +1,432 @@ - -# obs-websocket 5.x.x Protocol - -## Main Table of Contents - -- [General Intro](#general-intro) - - [Design Goals](#design-goals) -- [Connecting to obs-websocket](#connecting-to-obs-websocket) - - [Connection steps](#connection-steps) - - [Connection Notes](#connection-notes) - - [Creating an authentication string](#creating-an-authentication-string) -- [Message Types (OpCodes)](#message-types-opcodes) - - [Hello (OpCode 0)](#hello-opcode-0) - - [Identify (OpCode 1)](#identify-opcode-1) - - [Identified (OpCode 2)](#identified-opcode-2) - - [Reidentify (OpCode 3)](#reidentify-opcode-3) - - [Event (OpCode 5)](#event-opcode-5) - - [Request (OpCode 6)](#request-opcode-6) - - [RequestResponse (OpCode 7)](#requestresponse-opcode-7) - - [RequestBatch (OpCode 8)](#requestbatch-opcode-8) - - [RequestBatchResponse (OpCode 9)](#requestbatchresponse-opcode-9) -- [Enumerations](#enums) -- [Events](#events) -- [Requests](#requests) - -## General Intro - -obs-websocket provides a feature-rich RPC communication protocol, giving access to much of OBS's feature set. This document contains everything you should know in order to make a connection and use obs-websocket's functionality to the fullest. - -### Design Goals - -- Abstraction of identification, events, requests, and batch requests into dedicated message types -- Conformity of request naming using similar terms like `Get`, `Set`, `Get[x]List`, `Start[x]`, `Toggle[x]` -- Conformity of OBS data field names like `sourceName`, `sourceKind`, `sourceType`, `sceneName`, `sceneItemName` -- Error code response system - integer corrosponds to type of error, with optional comment -- Possible support for multiple message encoding options: JSON and MessagePack -- PubSub system - Allow clients to specify which events they do or don't want to receive from OBS -- RPC versioning - Client and server negotiate the latest version of the obs-websocket protocol to communicate with. - -## Connecting to obs-websocket - -Here's info on how to connect to obs-websocket - ---- - -### Connection steps - -These steps should be followed precisely. Failure to connect to the server as instructed will likely result in your client being treated in an undefined way. - -- Initial HTTP request made to the obs-websocket server. - - The `Sec-WebSocket-Protocol` header can be used to tell obs-websocket which kind of message encoding to use. By default, obs-websocket uses JSON over text. Available subprotocols: - - `obswebsocket.json` - JSON over text frames - - `obswebsocket.msgpack` - MsgPack over binary frames - -- Once the connection is upgraded, the websocket server will immediately send an [OpCode 0 `Hello`](#hello-opcode-0) message to the client. - -- The client listens for the `Hello` and responds with an [OpCode 1 `Identify`](#identify-opcode-1) containing all appropriate session parameters. - - If there is an `authentication` field in the `messageData` object, the server requires authentication, and the steps in [Creating an authentication string](#creating-an-authentication-string) should be followed. - - If there is no `authentication` field, the resulting `Identify` object sent to the server does not require an `authentication` string. - - The client determines if the server's `rpcVersion` is supported, and if not it provides its closest supported version in `Identify`. - -- The server receives and processes the `Identify` sent by the client. - - If authentication is required and the `Identify` message data does not contain an `authentication` string, or the string is not correct, the connection is closed with `WebSocketCloseCode::AuthenticationFailed` - - If the client has requested an `rpcVersion` which the server cannot use, the connection is closed with `WebSocketCloseCode::UnsupportedRpcVersion`. This system allows both the server and client to have seamless backwards compatibility. - - If any other parameters are malformed (invalid type, etc), the connection is closed with an appropriate close code. - -- Once identification is processed on the server, the server responds to the client with an [OpCode 2 `Identified`](#identified-opcode-2). - -- The client will begin receiving events from obs-websocket and may now make requests to obs-websocket. - -- At any time after a client has been identified, it may send an [OpCode 3 `Reidentify`](#reidentify-opcode-3) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification. - -#### Connection Notes - -- If a binary frame is received when using the `obswebsocket.json` (default) subprotocol, or a text frame is received while using the `obswebsocket.msgpack` subprotocol, the connection is closed with `WebSocketCloseCode::MessageDecodeError`. -- The obs-websocket server listens for any messages containing a `request-type` field in the first level JSON from unidentified clients. If a message matches, the connection is closed with `WebSocketCloseCode::UnsupportedRpcVersion` and a warning is logged. -- If a message with a `messageType` is not recognized to the obs-websocket server, the connection is closed with `WebSocketCloseCode::UnknownOpCode`. -- At no point may the client send any message other than a single `Identify` before it has received an `Identified`. Doing so will result in the connection being closed with `WebSocketCloseCode::NotIdentified`. - ---- - -### Creating an authentication string - -obs-websocket uses SHA256 to transmit authentication credentials. The server starts by sending an object in the `authentication` field of its `Hello` message data. The client processes the authentication challenge and responds via the `authentication` string in the `Identify` message data. - -For this guide, we'll be using `supersecretpassword` as the password. - -The `authentication` object in `Hello` looks like this (example): - -```json -{ - "challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=", - "salt": "lM1GncleQOaCu9lT1yeUZhFYnqhsLLP1G5lAGo3ixaI=" -} -``` - -To generate the authentication string, follow these steps: - -- Concatenate the websocket password with the `salt` provided by the server (`password + salt`) -- Generate an SHA256 binary hash of the result and base64 encode it, known as a base64 secret. -- Concatenate the base64 secret with the `challenge` sent by the server (`base64_secret + challenge`) -- Generate a binary SHA256 hash of that result and base64 encode it. You now have your `authentication` string. - -For real-world examples of the `authentication` string creation, refer to the obs-websocket client libraries listed on the [README](../../README.md). - -## Message Types (OpCodes) - -The following message types are the low-level message types which may be sent to and from obs-websocket. - -Messages sent from the obs-websocket server or client may contain these first-level fields, known as the base object: - -```txt -{ - "op": number, - "d": object -} -``` - -- `op` is a `WebSocketOpCode` OpCode. -- `d` is an object of the data fields associated with the operation. - ---- - -### Hello (OpCode 0) - -- Sent from: obs-websocket -- Sent to: Freshly connected websocket client -- Description: First message sent from the server immediately on client connection. Contains authentication information if auth is required. Also contains RPC version for version negotiation. - -**Data Keys:** - -```txt -{ - "obsStudioVersion": string, - "obsWebSocketVersion": string, - "rpcVersion": number, - "authentication": object(optional) -} -``` - -- `rpcVersion` is a version number which gets incremented on each **breaking change** to the obs-websocket protocol. Its usage in this context is to provide the current rpc version that the server would like to use. -- `obsWebSocketVersion` may be used as a soft feature level hint. For example, a new WebSocket request may only be available in a specific obs-websocket version or newer, but the rpcVersion will not be increased, as no - breaking changes have occured. Be aware, that no guarantees will be made on these assumptions, and you should still verify that the requests you desire to use are available in obs-websocket via the `GetVersion` request. - -**Example Messages:** -Authentication is required - -```json -{ - "op": 0, - "d": { - "obsStudioVersion": "30.2.2", - "obsWebSocketVersion": "5.5.2", - "rpcVersion": 1, - "authentication": { - "challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=", - "salt": "lM1GncleQOaCu9lT1yeUZhFYnqhsLLP1G5lAGo3ixaI=" - } - } -} -``` - -Authentication is not required - -```json -{ - "op": 0, - "d": { - "obsStudioVersion": "30.2.2", - "obsWebSocketVersion": "5.5.2", - "rpcVersion": 1 - } -} -``` - ---- - -### Identify (OpCode 1) - -- Sent from: Freshly connected websocket client -- Sent to: obs-websocket -- Description: Response to `Hello` message, should contain authentication string if authentication is required, along with PubSub subscriptions and other session parameters. - -**Data Keys:** - -```txt -{ - "rpcVersion": number, - "authentication": string(optional), - "eventSubscriptions": number(optional) = (EventSubscription::All) -} -``` - -- `rpcVersion` is the version number that the client would like the obs-websocket server to use. -- `eventSubscriptions` is a bitmask of `EventSubscriptions` items to subscribe to events and event categories at will. By default, all event categories are subscribed, except for events marked as high volume. High volume events must be explicitly subscribed to. - -**Example Message:** - -```json -{ - "op": 1, - "d": { - "rpcVersion": 1, - "authentication": "Dj6cLS+jrNA0HpCArRg0Z/Fc+YHdt2FQfAvgD1mip6Y=", - "eventSubscriptions": 33 - } -} -``` - ---- - -### Identified (OpCode 2) - -- Sent from: obs-websocket -- Sent to: Freshly identified client -- Description: The identify request was received and validated, and the connection is now ready for normal operation. - -**Data Keys:** - -```txt -{ - "negotiatedRpcVersion": number -} -``` - -- If rpc version negotiation succeeds, the server determines the RPC version to be used and gives it to the client as `negotiatedRpcVersion` - -**Example Message:** - -```json -{ - "op": 2, - "d": { - "negotiatedRpcVersion": 1 - } -} -``` - ---- - -### Reidentify (OpCode 3) - -- Sent from: Identified client -- Sent to: obs-websocket -- Description: Sent at any time after initial identification to update the provided session parameters. - -**Data Keys:** - -```txt -{ - "eventSubscriptions": number(optional) = (EventSubscription::All) -} -``` - -- Only the listed parameters may be changed after initial identification. To change a parameter not listed, you must reconnect to the obs-websocket server. - ---- - -### Event (OpCode 5) - -- Sent from: obs-websocket -- Sent to: All subscribed and identified clients -- Description: An event coming from OBS has occured. Eg scene switched, source muted. - -**Data Keys:** - -```txt -{ - "eventType": string, - "eventIntent": number, - "eventData": object(optional) -} -``` - -- `eventIntent` is the original intent required to be subscribed to in order to receive the event. - -**Example Message:** - -```json -{ - "op": 5, - "d": { - "eventType": "StudioModeStateChanged", - "eventIntent": 1, - "eventData": { - "studioModeEnabled": true - } - } -} -``` - ---- - -### Request (OpCode 6) - -- Sent from: Identified client -- Sent to: obs-websocket -- Description: Client is making a request to obs-websocket. Eg get current scene, create source. - -**Data Keys:** - -```txt -{ - "requestType": string, - "requestId": string, - "requestData": object(optional), - -} -``` - -**Example Message:** - -```json -{ - "op": 6, - "d": { - "requestType": "SetCurrentProgramScene", - "requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c", - "requestData": { - "sceneName": "Scene 12" - } - } -} -``` - ---- - -### RequestResponse (OpCode 7) - -- Sent from: obs-websocket -- Sent to: Identified client which made the request -- Description: obs-websocket is responding to a request coming from a client. - -**Data Keys:** - -```txt -{ - "requestType": string, - "requestId": string, - "requestStatus": object, - "responseData": object(optional) -} -``` - -- The `requestType` and `requestId` are simply mirrors of what was sent by the client. - -`requestStatus` object: - -```txt -{ - "result": bool, - "code": number, - "comment": string(optional) -} -``` - -- `result` is `true` if the request resulted in `RequestStatus::Success`. False if otherwise. -- `code` is a `RequestStatus` code. -- `comment` may be provided by the server on errors to offer further details on why a request failed. - -**Example Messages:** -Successful Response - -```json -{ - "op": 7, - "d": { - "requestType": "SetCurrentProgramScene", - "requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c", - "requestStatus": { - "result": true, - "code": 100 - } - } -} -``` - -Failure Response - -```json -{ - "op": 7, - "d": { - "requestType": "SetCurrentProgramScene", - "requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c", - "requestStatus": { - "result": false, - "code": 608, - "comment": "Parameter: sceneName" - } - } -} -``` - ---- - -### RequestBatch (OpCode 8) - -- Sent from: Identified client -- Sent to: obs-websocket -- Description: Client is making a batch of requests for obs-websocket. Requests are processed serially (in order) by the server. - -**Data Keys:** - -```txt -{ - "requestId": string, - "haltOnFailure": bool(optional) = false, - "executionType": number(optional) = RequestBatchExecutionType::SerialRealtime - "requests": array -} -``` - -- When `haltOnFailure` is `true`, the processing of requests will be halted on first failure. Returns only the processed requests in [`RequestBatchResponse`](#requestbatchresponse-opcode-9). -- Requests in the `requests` array follow the same structure as the `Request` payload data format, however `requestId` is an optional field. - ---- - -### RequestBatchResponse (OpCode 9) - -- Sent from: obs-websocket -- Sent to: Identified client which made the request -- Description: obs-websocket is responding to a request batch coming from the client. - -**Data Keys:** - -```txt -{ - "requestId": string, - "results": array -} -``` + +# obs-websocket 5.x.x Protocol + +## Main Table of Contents + +- [General Intro](#general-intro) + - [Design Goals](#design-goals) +- [Connecting to obs-websocket](#connecting-to-obs-websocket) + - [Connection steps](#connection-steps) + - [Connection Notes](#connection-notes) + - [Creating an authentication string](#creating-an-authentication-string) +- [Message Types (OpCodes)](#message-types-opcodes) + - [Hello (OpCode 0)](#hello-opcode-0) + - [Identify (OpCode 1)](#identify-opcode-1) + - [Identified (OpCode 2)](#identified-opcode-2) + - [Reidentify (OpCode 3)](#reidentify-opcode-3) + - [Event (OpCode 5)](#event-opcode-5) + - [Request (OpCode 6)](#request-opcode-6) + - [RequestResponse (OpCode 7)](#requestresponse-opcode-7) + - [RequestBatch (OpCode 8)](#requestbatch-opcode-8) + - [RequestBatchResponse (OpCode 9)](#requestbatchresponse-opcode-9) +- [Enumerations](#enums) +- [Events](#events) +- [Requests](#requests) + +## General Intro + +obs-websocket provides a feature-rich RPC communication protocol, giving access to much of OBS's feature set. This document contains everything you should know in order to make a connection and use obs-websocket's functionality to the fullest. + +### Design Goals + +- Abstraction of identification, events, requests, and batch requests into dedicated message types +- Conformity of request naming using similar terms like `Get`, `Set`, `Get[x]List`, `Start[x]`, `Toggle[x]` +- Conformity of OBS data field names like `sourceName`, `sourceKind`, `sourceType`, `sceneName`, `sceneItemName` +- Error code response system - integer corrosponds to type of error, with optional comment +- Possible support for multiple message encoding options: JSON and MessagePack +- PubSub system - Allow clients to specify which events they do or don't want to receive from OBS +- RPC versioning - Client and server negotiate the latest version of the obs-websocket protocol to communicate with. + +## Connecting to obs-websocket + +Here's info on how to connect to obs-websocket + +--- + +### Connection steps + +These steps should be followed precisely. Failure to connect to the server as instructed will likely result in your client being treated in an undefined way. + +- Initial HTTP request made to the obs-websocket server. + - The `Sec-WebSocket-Protocol` header can be used to tell obs-websocket which kind of message encoding to use. By default, obs-websocket uses JSON over text. Available subprotocols: + - `obswebsocket.json` - JSON over text frames + - `obswebsocket.msgpack` - MsgPack over binary frames + +- Once the connection is upgraded, the websocket server will immediately send an [OpCode 0 `Hello`](#hello-opcode-0) message to the client. + +- The client listens for the `Hello` and responds with an [OpCode 1 `Identify`](#identify-opcode-1) containing all appropriate session parameters. + - If there is an `authentication` field in the `messageData` object, the server requires authentication, and the steps in [Creating an authentication string](#creating-an-authentication-string) should be followed. + - If there is no `authentication` field, the resulting `Identify` object sent to the server does not require an `authentication` string. + - The client determines if the server's `rpcVersion` is supported, and if not it provides its closest supported version in `Identify`. + +- The server receives and processes the `Identify` sent by the client. + - If authentication is required and the `Identify` message data does not contain an `authentication` string, or the string is not correct, the connection is closed with `WebSocketCloseCode::AuthenticationFailed` + - If the client has requested an `rpcVersion` which the server cannot use, the connection is closed with `WebSocketCloseCode::UnsupportedRpcVersion`. This system allows both the server and client to have seamless backwards compatibility. + - If any other parameters are malformed (invalid type, etc), the connection is closed with an appropriate close code. + +- Once identification is processed on the server, the server responds to the client with an [OpCode 2 `Identified`](#identified-opcode-2). + +- The client will begin receiving events from obs-websocket and may now make requests to obs-websocket. + +- At any time after a client has been identified, it may send an [OpCode 3 `Reidentify`](#reidentify-opcode-3) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification. + +#### Connection Notes + +- If a binary frame is received when using the `obswebsocket.json` (default) subprotocol, or a text frame is received while using the `obswebsocket.msgpack` subprotocol, the connection is closed with `WebSocketCloseCode::MessageDecodeError`. +- The obs-websocket server listens for any messages containing a `request-type` field in the first level JSON from unidentified clients. If a message matches, the connection is closed with `WebSocketCloseCode::UnsupportedRpcVersion` and a warning is logged. +- If a message with a `messageType` is not recognized to the obs-websocket server, the connection is closed with `WebSocketCloseCode::UnknownOpCode`. +- At no point may the client send any message other than a single `Identify` before it has received an `Identified`. Doing so will result in the connection being closed with `WebSocketCloseCode::NotIdentified`. + +--- + +### Creating an authentication string + +obs-websocket uses SHA256 to transmit authentication credentials. The server starts by sending an object in the `authentication` field of its `Hello` message data. The client processes the authentication challenge and responds via the `authentication` string in the `Identify` message data. + +For this guide, we'll be using `supersecretpassword` as the password. + +The `authentication` object in `Hello` looks like this (example): + +```json +{ + "challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=", + "salt": "lM1GncleQOaCu9lT1yeUZhFYnqhsLLP1G5lAGo3ixaI=" +} +``` + +To generate the authentication string, follow these steps: + +- Concatenate the websocket password with the `salt` provided by the server (`password + salt`) +- Generate an SHA256 binary hash of the result and base64 encode it, known as a base64 secret. +- Concatenate the base64 secret with the `challenge` sent by the server (`base64_secret + challenge`) +- Generate a binary SHA256 hash of that result and base64 encode it. You now have your `authentication` string. + +For real-world examples of the `authentication` string creation, refer to the obs-websocket client libraries listed on the [README](../../README.md). + +## Message Types (OpCodes) + +The following message types are the low-level message types which may be sent to and from obs-websocket. + +Messages sent from the obs-websocket server or client may contain these first-level fields, known as the base object: + +```txt +{ + "op": number, + "d": object +} +``` + +- `op` is a `WebSocketOpCode` OpCode. +- `d` is an object of the data fields associated with the operation. + +--- + +### Hello (OpCode 0) + +- Sent from: obs-websocket +- Sent to: Freshly connected websocket client +- Description: First message sent from the server immediately on client connection. Contains authentication information if auth is required. Also contains RPC version for version negotiation. + +**Data Keys:** + +```txt +{ + "obsStudioVersion": string, + "obsWebSocketVersion": string, + "rpcVersion": number, + "authentication": object(optional) +} +``` + +- `rpcVersion` is a version number which gets incremented on each **breaking change** to the obs-websocket protocol. Its usage in this context is to provide the current rpc version that the server would like to use. +- `obsWebSocketVersion` may be used as a soft feature level hint. For example, a new WebSocket request may only be available in a specific obs-websocket version or newer, but the rpcVersion will not be increased, as no + breaking changes have occured. Be aware, that no guarantees will be made on these assumptions, and you should still verify that the requests you desire to use are available in obs-websocket via the `GetVersion` request. + +**Example Messages:** +Authentication is required + +```json +{ + "op": 0, + "d": { + "obsStudioVersion": "30.2.2", + "obsWebSocketVersion": "5.5.2", + "rpcVersion": 1, + "authentication": { + "challenge": "+IxH4CnCiqpX1rM9scsNynZzbOe4KhDeYcTNS3PDaeY=", + "salt": "lM1GncleQOaCu9lT1yeUZhFYnqhsLLP1G5lAGo3ixaI=" + } + } +} +``` + +Authentication is not required + +```json +{ + "op": 0, + "d": { + "obsStudioVersion": "30.2.2", + "obsWebSocketVersion": "5.5.2", + "rpcVersion": 1 + } +} +``` + +--- + +### Identify (OpCode 1) + +- Sent from: Freshly connected websocket client +- Sent to: obs-websocket +- Description: Response to `Hello` message, should contain authentication string if authentication is required, along with PubSub subscriptions and other session parameters. + +**Data Keys:** + +```txt +{ + "rpcVersion": number, + "authentication": string(optional), + "eventSubscriptions": number(optional) = (EventSubscription::All) +} +``` + +- `rpcVersion` is the version number that the client would like the obs-websocket server to use. +- `eventSubscriptions` is a bitmask of `EventSubscriptions` items to subscribe to events and event categories at will. By default, all event categories are subscribed, except for events marked as high volume. High volume events must be explicitly subscribed to. + +**Example Message:** + +```json +{ + "op": 1, + "d": { + "rpcVersion": 1, + "authentication": "Dj6cLS+jrNA0HpCArRg0Z/Fc+YHdt2FQfAvgD1mip6Y=", + "eventSubscriptions": 33 + } +} +``` + +--- + +### Identified (OpCode 2) + +- Sent from: obs-websocket +- Sent to: Freshly identified client +- Description: The identify request was received and validated, and the connection is now ready for normal operation. + +**Data Keys:** + +```txt +{ + "negotiatedRpcVersion": number +} +``` + +- If rpc version negotiation succeeds, the server determines the RPC version to be used and gives it to the client as `negotiatedRpcVersion` + +**Example Message:** + +```json +{ + "op": 2, + "d": { + "negotiatedRpcVersion": 1 + } +} +``` + +--- + +### Reidentify (OpCode 3) + +- Sent from: Identified client +- Sent to: obs-websocket +- Description: Sent at any time after initial identification to update the provided session parameters. + +**Data Keys:** + +```txt +{ + "eventSubscriptions": number(optional) = (EventSubscription::All) +} +``` + +- Only the listed parameters may be changed after initial identification. To change a parameter not listed, you must reconnect to the obs-websocket server. + +--- + +### Event (OpCode 5) + +- Sent from: obs-websocket +- Sent to: All subscribed and identified clients +- Description: An event coming from OBS has occured. Eg scene switched, source muted. + +**Data Keys:** + +```txt +{ + "eventType": string, + "eventIntent": number, + "eventData": object(optional) +} +``` + +- `eventIntent` is the original intent required to be subscribed to in order to receive the event. + +**Example Message:** + +```json +{ + "op": 5, + "d": { + "eventType": "StudioModeStateChanged", + "eventIntent": 1, + "eventData": { + "studioModeEnabled": true + } + } +} +``` + +--- + +### Request (OpCode 6) + +- Sent from: Identified client +- Sent to: obs-websocket +- Description: Client is making a request to obs-websocket. Eg get current scene, create source. + +**Data Keys:** + +```txt +{ + "requestType": string, + "requestId": string, + "requestData": object(optional), + +} +``` + +**Example Message:** + +```json +{ + "op": 6, + "d": { + "requestType": "SetCurrentProgramScene", + "requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c", + "requestData": { + "sceneName": "Scene 12" + } + } +} +``` + +--- + +### RequestResponse (OpCode 7) + +- Sent from: obs-websocket +- Sent to: Identified client which made the request +- Description: obs-websocket is responding to a request coming from a client. + +**Data Keys:** + +```txt +{ + "requestType": string, + "requestId": string, + "requestStatus": object, + "responseData": object(optional) +} +``` + +- The `requestType` and `requestId` are simply mirrors of what was sent by the client. + +`requestStatus` object: + +```txt +{ + "result": bool, + "code": number, + "comment": string(optional) +} +``` + +- `result` is `true` if the request resulted in `RequestStatus::Success`. False if otherwise. +- `code` is a `RequestStatus` code. +- `comment` may be provided by the server on errors to offer further details on why a request failed. + +**Example Messages:** +Successful Response + +```json +{ + "op": 7, + "d": { + "requestType": "SetCurrentProgramScene", + "requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c", + "requestStatus": { + "result": true, + "code": 100 + } + } +} +``` + +Failure Response + +```json +{ + "op": 7, + "d": { + "requestType": "SetCurrentProgramScene", + "requestId": "f819dcf0-89cc-11eb-8f0e-382c4ac93b9c", + "requestStatus": { + "result": false, + "code": 608, + "comment": "Parameter: sceneName" + } + } +} +``` + +--- + +### RequestBatch (OpCode 8) + +- Sent from: Identified client +- Sent to: obs-websocket +- Description: Client is making a batch of requests for obs-websocket. Requests are processed serially (in order) by the server. + +**Data Keys:** + +```txt +{ + "requestId": string, + "haltOnFailure": bool(optional) = false, + "executionType": number(optional) = RequestBatchExecutionType::SerialRealtime + "requests": array +} +``` + +- When `haltOnFailure` is `true`, the processing of requests will be halted on first failure. Returns only the processed requests in [`RequestBatchResponse`](#requestbatchresponse-opcode-9). +- Requests in the `requests` array follow the same structure as the `Request` payload data format, however `requestId` is an optional field. + +--- + +### RequestBatchResponse (OpCode 9) + +- Sent from: obs-websocket +- Sent to: Identified client which made the request +- Description: obs-websocket is responding to a request batch coming from the client. + +**Data Keys:** + +```txt +{ + "requestId": string, + "results": array +} +``` diff --git a/docs/docs/partials/requestsHeader.md b/docs/docs/partials/requestsHeader.md index 2a0c40f8..7850363f 100644 --- a/docs/docs/partials/requestsHeader.md +++ b/docs/docs/partials/requestsHeader.md @@ -1,3 +1,3 @@ -# Requests - -## Requests Table of Contents +# Requests + +## Requests Table of Contents diff --git a/docs/docs/process_comments.py b/docs/docs/process_comments.py index 3868eb5e..3c76de64 100644 --- a/docs/docs/process_comments.py +++ b/docs/docs/process_comments.py @@ -1,208 +1,242 @@ -import logging -logging.basicConfig(level=logging.INFO, format="%(asctime)s [process_comments.py] [%(levelname)s] %(message)s") -import os -import sys -import json - -# The comments parser will return a string type instead of an array if there is only one field -def field_to_array(field): - if type(field) == str: - return [field] - return field - -# This raw JSON can be really damn unpredictable. Let's handle that -def field_to_string(field): - if type(field) == list: - return field_to_string(field[0]) - elif type(field) == dict: - return field_to_string(field['description']) - return str(field) - -# Make sure that everything we expect is there -def validate_fields(data, fields): - for field in fields: - if field not in data: - logging.warning('Missing required item: {}'.format(field)) - return False - return True - -# Get the individual components of a `requestField` or `responseField` or `dataField` entry -def get_components(data): - ret = [] - components_raw = data.split('|') - for component in components_raw: - ret.append(component.strip()) - return ret - -# Convert all request fields from raw to final -def get_request_fields(fields): - fields = field_to_array(fields) - ret = [] - for field in fields: - components = get_components(field) - field_out = {} - field_out['valueName'] = components[0].replace('?', '') - field_out['valueType'] = components[1] - field_out['valueDescription'] = components[2] - - valueOptionalOffset = 3 - # If value type is a number, restrictions are required. Else, should not be added. - if field_out['valueType'].lower() == 'number': - # In the case of a number, the optional component gets pushed back. - valueOptionalOffset += 1 - field_out['valueRestrictions'] = components[3] if components[3].lower() != 'none' else None - else: - field_out['valueRestrictions'] = None - - field_out['valueOptional'] = components[0].startswith('?') - if field_out['valueOptional']: - field_out['valueOptionalBehavior'] = components[valueOptionalOffset] if len(components) > valueOptionalOffset else 'Unknown' - else: - field_out['valueOptionalBehavior'] = None - ret.append(field_out) - return ret - -# Convert all response (or event data) fields from raw to final -def get_response_fields(fields): - fields = field_to_array(fields) - ret = [] - for field in fields: - components = get_components(field) - field_out = {} - field_out['valueName'] = components[0] - field_out['valueType'] = components[1] - field_out['valueDescription'] = components[2] - ret.append(field_out) - return ret - -####################################################################################################################### - -# Read versions json -try: - with open('../versions.json', 'r') as f: - versions = json.load(f) -except IOError: - logging.error('Failed to get global versions. Versions file not configured?') - os.exit(1) - -# Read the raw comments output file -with open('../work/comments.json', 'r') as f: - comments_raw = json.load(f) - -# Prepare output variables -enums = [] -requests = [] -events = [] - -enums_raw = {} -# Process the raw comments -for comment in comments_raw: - # Skip unrelated comments like #include - if 'api' not in comment: - continue - - api = comment['api'] - if api == 'enums': - if not validate_fields(comment, ['description', 'enumIdentifier', 'enumType', 'rpcVersion', 'initialVersion']): - logging.warning('Failed to process enum id comment due to missing field(s):\n{}'.format(comment)) - continue - - enumType = field_to_string(comment['enumType']) - - enum = {} - # Recombines the header back into one string, allowing multi-line descriptions. - enum['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description']) - enum['enumIdentifier'] = field_to_string(comment['enumIdentifier']) - rpcVersionRaw = field_to_string(comment['rpcVersion']) - enum['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw) - enum['deprecated'] = False if rpcVersionRaw == '-1' else True - enum['initialVersion'] = field_to_string(comment['initialVersion']) - - if 'enumValue' in comment: - enumValue = field_to_string(comment['enumValue']) - enum['enumValue'] = int(enumValue) if enumValue.isdigit() else enumValue - else: - enum['enumValue'] = enum['enumIdentifier'] - - if enumType not in enums_raw: - enums_raw[enumType] = {'enumIdentifiers': [enum]} - else: - enums_raw[enumType]['enumIdentifiers'].append(enum) - - logging.info('Processed enum: {}::{}'.format(enumType, enum['enumIdentifier'])) - elif api == 'requests': - if not validate_fields(comment, ['description', 'requestType', 'complexity', 'rpcVersion', 'initialVersion', 'category']): - logging.warning('Failed to process request comment due to missing field(s):\n{}'.format(comment)) - continue - - req = {} - req['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description']) - req['requestType'] = field_to_string(comment['requestType']) - req['complexity'] = int(field_to_string(comment['complexity'])) - rpcVersionRaw = field_to_string(comment['rpcVersion']) - req['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw) - req['deprecated'] = False if rpcVersionRaw == '-1' else True - req['initialVersion'] = field_to_string(comment['initialVersion']) - req['category'] = field_to_string(comment['category']) - - try: - if 'requestField' in comment: - req['requestFields'] = get_request_fields(comment['requestField']) - else: - req['requestFields'] = [] - except: - logging.exception('Failed to process request `{}` request fields due to error:\n'.format(req['requestType'])) - continue - - try: - if 'responseField' in comment: - req['responseFields'] = get_response_fields(comment['responseField']) - else: - req['responseFields'] = [] - except: - logging.exception('Failed to process request `{}` request fields due to error:\n'.format(req['requestType'])) - continue - - logging.info('Processed request: {}'.format(req['requestType'])) - - requests.append(req) - elif api == 'events': - if not validate_fields(comment, ['description', 'eventType', 'eventSubscription', 'complexity', 'rpcVersion', 'initialVersion', 'category']): - logging.warning('Failed to process event comment due to missing field(s):\n{}'.format(comment)) - continue - - eve = {} - eve['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description']) - eve['eventType'] = field_to_string(comment['eventType']) - eve['eventSubscription'] = field_to_string(comment['eventSubscription']) - eve['complexity'] = int(field_to_string(comment['complexity'])) - rpcVersionRaw = field_to_string(comment['rpcVersion']) - eve['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw) - eve['deprecated'] = False if rpcVersionRaw == '-1' else True - eve['initialVersion'] = field_to_string(comment['initialVersion']) - eve['category'] = field_to_string(comment['category']) - - try: - if 'dataField' in comment: - eve['dataFields'] = get_response_fields(comment['dataField']) - else: - eve['dataFields'] = [] - except: - logging.exception('Failed to process event `{}` data fields due to error:\n'.format(req['eventType'])) - continue - - logging.info('Processed event: {}'.format(eve['eventType'])) - - events.append(eve) - else: - logging.warning('Comment with unknown api: {}'.format(api)) - -# Reconfigure enums to match the correct structure -for enumType in enums_raw.keys(): - enum = enums_raw[enumType] - enums.append({'enumType': enumType, 'enumIdentifiers': enum['enumIdentifiers']}) - -finalObject = {'enums': enums, 'requests': requests, 'events': events} - -with open('../generated/protocol.json', 'w') as f: - json.dump(finalObject, f, indent=2) +import logging +logging.basicConfig(level=logging.INFO, format="%(asctime)s [process_comments.py] [%(levelname)s] %(message)s") +import os +import sys +import json + +# The comments parser will return a string type instead of an array if there is only one field +def field_to_array(field): + if type(field) == str: + return [field] + return field + +# This raw JSON can be really damn unpredictable. Let's handle that +def field_to_string(field): + if type(field) == list: + return field_to_string(field[0]) + elif type(field) == dict: + return field_to_string(field['description']) + return str(field) + +# Make sure that everything we expect is there +def validate_fields(data, fields): + for field in fields: + if field not in data: + logging.warning('Missing required item: {}'.format(field)) + return False + return True + +# Get the individual components of a `requestField` or `responseField` or `dataField` or `field` entry +def get_components(data): + ret = [] + components_raw = data.split('|') + for component in components_raw: + ret.append(component.strip()) + return ret + +# Convert all typedef fields from raw to final +def get_typedef_fields(fields): + fields = field_to_array(fields) + ret = [] + for field in fields: + components = get_components(field) + field_out = {} + field_out['fieldName'] = components[0] + field_out['fieldType'] = components[1] + field_out['fieldDescription'] = components[2] if len(components) > 2 else '' + ret.append(field_out) + return ret + +# Convert all request fields from raw to final +def get_request_fields(fields): + fields = field_to_array(fields) + ret = [] + for field in fields: + components = get_components(field) + field_out = {} + field_out['valueName'] = components[0].replace('?', '') + field_out['valueType'] = components[1] + field_out['valueDescription'] = components[2] + + valueOptionalOffset = 3 + # If value type is a number, restrictions are required. Else, should not be added. + if field_out['valueType'].lower() == 'number': + # In the case of a number, the optional component gets pushed back. + valueOptionalOffset += 1 + field_out['valueRestrictions'] = components[3] if components[3].lower() != 'none' else None + else: + field_out['valueRestrictions'] = None + + field_out['valueOptional'] = components[0].startswith('?') + if field_out['valueOptional']: + field_out['valueOptionalBehavior'] = components[valueOptionalOffset] if len(components) > valueOptionalOffset else 'Unknown' + else: + field_out['valueOptionalBehavior'] = None + ret.append(field_out) + return ret + +# Convert all response (or event data) fields from raw to final +def get_response_fields(fields): + fields = field_to_array(fields) + ret = [] + for field in fields: + components = get_components(field) + field_out = {} + field_out['valueName'] = components[0] + field_out['valueType'] = components[1] + field_out['valueDescription'] = components[2] + ret.append(field_out) + return ret + +####################################################################################################################### + +# Read versions json +try: + with open('../versions.json', 'r') as f: + versions = json.load(f) +except IOError: + logging.error('Failed to get global versions. Versions file not configured?') + os.exit(1) + +# Read the raw comments output file +with open('../work/comments.json', 'r') as f: + comments_raw = json.load(f) + +# Prepare output variables +types = [] +enums = [] +requests = [] +events = [] + +enums_raw = {} +# Process the raw comments +for comment in comments_raw: + # Skip unrelated comments like #include + if 'api' not in comment: + continue + + api = comment['api'] + if api == 'types': + if not validate_fields(comment, ['typedef']): + logging.warning('Failed to process type comment due to missing field(s):\n{}'.format(comment)) + continue + + typedef = {} + typedef['typeName'] = field_to_string(comment['typedef']) + typedef['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment.get('description', '')) + + try: + if 'field' in comment: + typedef['typeFields'] = get_typedef_fields(comment['field']) + else: + typedef['typeFields'] = [] + except: + logging.exception('Failed to process type `{}` fields due to error:\n'.format(typedef['typeName'])) + continue + + types.append(typedef) + logging.info('Processed type: {}'.format(typedef['typeName'])) + elif api == 'enums': + if not validate_fields(comment, ['description', 'enumIdentifier', 'enumType', 'rpcVersion', 'initialVersion']): + logging.warning('Failed to process enum id comment due to missing field(s):\n{}'.format(comment)) + continue + + enumType = field_to_string(comment['enumType']) + + enum = {} + # Recombines the header back into one string, allowing multi-line descriptions. + enum['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description']) + enum['enumIdentifier'] = field_to_string(comment['enumIdentifier']) + rpcVersionRaw = field_to_string(comment['rpcVersion']) + enum['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw) + enum['deprecated'] = False if rpcVersionRaw == '-1' else True + enum['initialVersion'] = field_to_string(comment['initialVersion']) + + if 'enumValue' in comment: + enumValue = field_to_string(comment['enumValue']) + enum['enumValue'] = int(enumValue) if enumValue.isdigit() else enumValue + else: + enum['enumValue'] = enum['enumIdentifier'] + + if enumType not in enums_raw: + enums_raw[enumType] = {'enumIdentifiers': [enum]} + else: + enums_raw[enumType]['enumIdentifiers'].append(enum) + + logging.info('Processed enum: {}::{}'.format(enumType, enum['enumIdentifier'])) + elif api == 'requests': + if not validate_fields(comment, ['description', 'requestType', 'complexity', 'rpcVersion', 'initialVersion', 'category']): + logging.warning('Failed to process request comment due to missing field(s):\n{}'.format(comment)) + continue + + req = {} + req['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description']) + req['requestType'] = field_to_string(comment['requestType']) + req['complexity'] = int(field_to_string(comment['complexity'])) + rpcVersionRaw = field_to_string(comment['rpcVersion']) + req['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw) + req['deprecated'] = False if rpcVersionRaw == '-1' else True + req['initialVersion'] = field_to_string(comment['initialVersion']) + req['category'] = field_to_string(comment['category']) + + try: + if 'requestField' in comment: + req['requestFields'] = get_request_fields(comment['requestField']) + else: + req['requestFields'] = [] + except: + logging.exception('Failed to process request `{}` request fields due to error:\n'.format(req['requestType'])) + continue + + try: + if 'responseField' in comment: + req['responseFields'] = get_response_fields(comment['responseField']) + else: + req['responseFields'] = [] + except: + logging.exception('Failed to process request `{}` request fields due to error:\n'.format(req['requestType'])) + continue + + logging.info('Processed request: {}'.format(req['requestType'])) + + requests.append(req) + elif api == 'events': + if not validate_fields(comment, ['description', 'eventType', 'eventSubscription', 'complexity', 'rpcVersion', 'initialVersion', 'category']): + logging.warning('Failed to process event comment due to missing field(s):\n{}'.format(comment)) + continue + + eve = {} + eve['description'] = field_to_string(comment.get('lead', '')) + field_to_string(comment['description']) + eve['eventType'] = field_to_string(comment['eventType']) + eve['eventSubscription'] = field_to_string(comment['eventSubscription']) + eve['complexity'] = int(field_to_string(comment['complexity'])) + rpcVersionRaw = field_to_string(comment['rpcVersion']) + eve['rpcVersion'] = versions['rpcVersion'] if rpcVersionRaw == '-1' else int(rpcVersionRaw) + eve['deprecated'] = False if rpcVersionRaw == '-1' else True + eve['initialVersion'] = field_to_string(comment['initialVersion']) + eve['category'] = field_to_string(comment['category']) + + try: + if 'dataField' in comment: + eve['dataFields'] = get_response_fields(comment['dataField']) + else: + eve['dataFields'] = [] + except: + logging.exception('Failed to process event `{}` data fields due to error:\n'.format(req['eventType'])) + continue + + logging.info('Processed event: {}'.format(eve['eventType'])) + + events.append(eve) + else: + logging.warning('Comment with unknown api: {}'.format(api)) + +# Reconfigure enums to match the correct structure +for enumType in enums_raw.keys(): + enum = enums_raw[enumType] + enums.append({'enumType': enumType, 'enumIdentifiers': enum['enumIdentifiers']}) + +finalObject = {'types': types, 'enums': enums, 'requests': requests, 'events': events} + +with open('../generated/protocol.json', 'w') as f: + json.dump(finalObject, f, indent=2) diff --git a/docs/generated/protocol.json b/docs/generated/protocol.json index fc3a9624..9727b61e 100644 --- a/docs/generated/protocol.json +++ b/docs/generated/protocol.json @@ -1,4 +1,406 @@ { + "types": [ + { + "typeName": "Filter", + "description": "Source filter information.", + "typeFields": [ + { + "fieldName": "filterEnabled", + "fieldType": "Boolean", + "fieldDescription": "Whether the filter is enabled" + }, + { + "fieldName": "filterIndex", + "fieldType": "Number", + "fieldDescription": "Index position of the filter" + }, + { + "fieldName": "filterKind", + "fieldType": "String", + "fieldDescription": "Kind of the filter" + }, + { + "fieldName": "filterName", + "fieldType": "String", + "fieldDescription": "Name of the filter" + }, + { + "fieldName": "filterSettings", + "fieldType": "Object", + "fieldDescription": "Settings object associated with the filter" + } + ] + }, + { + "typeName": "Input", + "description": "Input source information.", + "typeFields": [ + { + "fieldName": "inputName", + "fieldType": "String", + "fieldDescription": "Name of the input" + }, + { + "fieldName": "inputUuid", + "fieldType": "String", + "fieldDescription": "UUID of the input" + }, + { + "fieldName": "inputKind", + "fieldType": "String", + "fieldDescription": "Kind of the input" + }, + { + "fieldName": "unversionedInputKind", + "fieldType": "String", + "fieldDescription": "Unversioned kind of the input (can be used across OBS versions)" + }, + { + "fieldName": "inputKindCaps", + "fieldType": "Number", + "fieldDescription": "Capabilities of the input kind" + } + ] + }, + { + "typeName": "PropertyItem", + "description": "Property list item information.", + "typeFields": [ + { + "fieldName": "itemName", + "fieldType": "String", + "fieldDescription": "Name of the item" + }, + { + "fieldName": "itemEnabled", + "fieldType": "Boolean", + "fieldDescription": "Whether the item is enabled" + }, + { + "fieldName": "itemValue", + "fieldType": "Any", + "fieldDescription": "Value of the item" + } + ] + }, + { + "typeName": "OutputFlags", + "description": "Output flags information.", + "typeFields": [ + { + "fieldName": "OBS_OUTPUT_AUDIO", + "fieldType": "Boolean", + "fieldDescription": "Output has audio capability" + }, + { + "fieldName": "OBS_OUTPUT_VIDEO", + "fieldType": "Boolean", + "fieldDescription": "Output has video capability" + }, + { + "fieldName": "OBS_OUTPUT_ENCODED", + "fieldType": "Boolean", + "fieldDescription": "Output uses encoded data" + }, + { + "fieldName": "OBS_OUTPUT_MULTI_TRACK", + "fieldType": "Boolean", + "fieldDescription": "Output supports multi-track audio" + }, + { + "fieldName": "OBS_OUTPUT_SERVICE", + "fieldType": "Boolean", + "fieldDescription": "Output is bound to a service" + } + ] + }, + { + "typeName": "Output", + "description": "Output information.", + "typeFields": [ + { + "fieldName": "outputName", + "fieldType": "String", + "fieldDescription": "Name of the output" + }, + { + "fieldName": "outputKind", + "fieldType": "String", + "fieldDescription": "Kind of the output" + }, + { + "fieldName": "outputWidth", + "fieldType": "Number", + "fieldDescription": "Width of the output in pixels" + }, + { + "fieldName": "outputHeight", + "fieldType": "Number", + "fieldDescription": "Height of the output in pixels" + }, + { + "fieldName": "outputActive", + "fieldType": "Boolean", + "fieldDescription": "Whether the output is active" + }, + { + "fieldName": "outputFlags", + "fieldType": "OutputFlags", + "fieldDescription": "Output capability flags" + } + ] + }, + { + "typeName": "SceneItemTransform", + "description": "Scene item transform information.", + "typeFields": [ + { + "fieldName": "sourceWidth", + "fieldType": "Number", + "fieldDescription": "Width of the source associated with the scene item" + }, + { + "fieldName": "sourceHeight", + "fieldType": "Number", + "fieldDescription": "Height of the source associated with the scene item" + }, + { + "fieldName": "positionX", + "fieldType": "Number", + "fieldDescription": "The x position of the scene item from the left" + }, + { + "fieldName": "positionY", + "fieldType": "Number", + "fieldDescription": "The y position of the scene item from the top" + }, + { + "fieldName": "rotation", + "fieldType": "Number", + "fieldDescription": "The clockwise rotation of the scene item in degrees around the point of alignment" + }, + { + "fieldName": "scaleX", + "fieldType": "Number", + "fieldDescription": "The x-scale factor of the scene item" + }, + { + "fieldName": "scaleY", + "fieldType": "Number", + "fieldDescription": "The y-scale factor of the scene item" + }, + { + "fieldName": "width", + "fieldType": "Number", + "fieldDescription": "The rendered width of the scene item" + }, + { + "fieldName": "height", + "fieldType": "Number", + "fieldDescription": "The rendered height of the scene item" + }, + { + "fieldName": "alignment", + "fieldType": "Number", + "fieldDescription": "The alignment of the scene item" + }, + { + "fieldName": "boundsType", + "fieldType": "String", + "fieldDescription": "Type of bounding box" + }, + { + "fieldName": "boundsAlignment", + "fieldType": "Number", + "fieldDescription": "Alignment of the bounding box" + }, + { + "fieldName": "boundsWidth", + "fieldType": "Number", + "fieldDescription": "Width of the bounding box" + }, + { + "fieldName": "boundsHeight", + "fieldType": "Number", + "fieldDescription": "Height of the bounding box" + }, + { + "fieldName": "cropLeft", + "fieldType": "Number", + "fieldDescription": "The number of pixels cropped off the left of the source before scaling" + }, + { + "fieldName": "cropRight", + "fieldType": "Number", + "fieldDescription": "The number of pixels cropped off the right of the source before scaling" + }, + { + "fieldName": "cropTop", + "fieldType": "Number", + "fieldDescription": "The number of pixels cropped off the top of the source before scaling" + }, + { + "fieldName": "cropBottom", + "fieldType": "Number", + "fieldDescription": "The number of pixels cropped off the bottom of the source before scaling" + }, + { + "fieldName": "cropToBounds", + "fieldType": "Boolean", + "fieldDescription": "Whether the crop is to the bounding box or to the scene item bounding box" + } + ] + }, + { + "typeName": "SceneItem", + "description": "Scene item information.", + "typeFields": [ + { + "fieldName": "sceneItemId", + "fieldType": "Number", + "fieldDescription": "Numeric ID of the scene item" + }, + { + "fieldName": "sceneItemIndex", + "fieldType": "Number", + "fieldDescription": "Index position of the scene item" + }, + { + "fieldName": "sceneItemEnabled", + "fieldType": "Boolean", + "fieldDescription": "Whether the scene item is enabled" + }, + { + "fieldName": "sceneItemLocked", + "fieldType": "Boolean", + "fieldDescription": "Whether the scene item is locked" + }, + { + "fieldName": "sceneItemTransform", + "fieldType": "SceneItemTransform", + "fieldDescription": "Transform information of the scene item" + }, + { + "fieldName": "sceneItemBlendMode", + "fieldType": "Number", + "fieldDescription": "Blend mode of the scene item" + }, + { + "fieldName": "sourceName", + "fieldType": "String", + "fieldDescription": "Name of the source associated with the scene item" + }, + { + "fieldName": "sourceUuid", + "fieldType": "String", + "fieldDescription": "UUID of the source associated with the scene item" + }, + { + "fieldName": "sourceType", + "fieldType": "Number", + "fieldDescription": "Type of the source" + }, + { + "fieldName": "inputKind", + "fieldType": "String", + "fieldDescription": "The kind of input source (null if not an input)" + }, + { + "fieldName": "isGroup", + "fieldType": "Boolean", + "fieldDescription": "Whether the source is a group (null if not a scene)" + } + ] + }, + { + "typeName": "Scene", + "description": "Scene information.", + "typeFields": [ + { + "fieldName": "sceneName", + "fieldType": "String", + "fieldDescription": "Name of the scene" + }, + { + "fieldName": "sceneUuid", + "fieldType": "String", + "fieldDescription": "UUID of the scene" + }, + { + "fieldName": "sceneIndex", + "fieldType": "Number", + "fieldDescription": "Index of the scene" + } + ] + }, + { + "typeName": "Transition", + "description": "Scene transition information.", + "typeFields": [ + { + "fieldName": "transitionName", + "fieldType": "String", + "fieldDescription": "Name of the transition" + }, + { + "fieldName": "transitionUuid", + "fieldType": "String", + "fieldDescription": "UUID of the transition" + }, + { + "fieldName": "transitionKind", + "fieldType": "String", + "fieldDescription": "Kind of the transition" + }, + { + "fieldName": "transitionFixed", + "fieldType": "Boolean", + "fieldDescription": "Whether the transition has fixed duration" + }, + { + "fieldName": "transitionConfigurable", + "fieldType": "Boolean", + "fieldDescription": "Whether the transition is configurable" + } + ] + }, + { + "typeName": "Monitor", + "description": "Monitor/display information.", + "typeFields": [ + { + "fieldName": "monitorName", + "fieldType": "String", + "fieldDescription": "Name and index of the monitor" + }, + { + "fieldName": "monitorIndex", + "fieldType": "Number", + "fieldDescription": "Index of the monitor" + }, + { + "fieldName": "monitorWidth", + "fieldType": "Number", + "fieldDescription": "Width of the monitor in pixels" + }, + { + "fieldName": "monitorHeight", + "fieldType": "Number", + "fieldDescription": "Height of the monitor in pixels" + }, + { + "fieldName": "monitorPositionX", + "fieldType": "Number", + "fieldDescription": "X position of the monitor" + }, + { + "fieldName": "monitorPositionY", + "fieldType": "Number", + "fieldDescription": "Y position of the monitor" + } + ] + } + ], "enums": [ { "enumType": "EventSubscription", @@ -1305,7 +1707,7 @@ "responseFields": [ { "valueName": "filters", - "valueType": "Array", + "valueType": "Array", "valueDescription": "Array of filters" } ] @@ -2005,7 +2407,7 @@ "responseFields": [ { "valueName": "inputs", - "valueType": "Array", + "valueType": "Array", "valueDescription": "Array of inputs" } ] @@ -2966,7 +3368,7 @@ "responseFields": [ { "valueName": "propertyItems", - "valueType": "Array", + "valueType": "Array", "valueDescription": "Array of items in the list property" } ] @@ -3311,7 +3713,7 @@ "responseFields": [ { "valueName": "outputs", - "valueType": "Array", + "valueType": "Array", "valueDescription": "Array of outputs" } ] @@ -3672,7 +4074,7 @@ "responseFields": [ { "valueName": "sceneItems", - "valueType": "Array", + "valueType": "Array", "valueDescription": "Array of scene items in the scene" } ] @@ -3706,7 +4108,7 @@ "responseFields": [ { "valueName": "sceneItems", - "valueType": "Array", + "valueType": "Array", "valueDescription": "Array of scene items in the group" } ] @@ -3997,7 +4399,7 @@ "responseFields": [ { "valueName": "sceneItemTransform", - "valueType": "Object", + "valueType": "SceneItemTransform", "valueDescription": "Object containing scene item transform info" } ] @@ -4037,7 +4439,7 @@ }, { "valueName": "sceneItemTransform", - "valueType": "Object", + "valueType": "SceneItemTransform", "valueDescription": "Object containing scene item transform info to update", "valueRestrictions": null, "valueOptional": false, @@ -4422,7 +4824,7 @@ }, { "valueName": "scenes", - "valueType": "Array", + "valueType": "Array", "valueDescription": "Array of scenes" } ] @@ -5065,7 +5467,7 @@ }, { "valueName": "transitions", - "valueType": "Array", + "valueType": "Array", "valueDescription": "Array of transitions" } ] @@ -5374,7 +5776,7 @@ "responseFields": [ { "valueName": "monitors", - "valueType": "Array", + "valueType": "Array", "valueDescription": "a list of detected monitors with some information" } ] diff --git a/docs/generated/protocol.md b/docs/generated/protocol.md index 13ec241d..ddcfd9c7 100644 --- a/docs/generated/protocol.md +++ b/docs/generated/protocol.md @@ -433,6 +433,209 @@ Failure Response } ``` +# Types + +The following types are used as complex object structures in various requests and events. + +## Table of Contents + +- [Filter](#filter) +- [Input](#input) +- [PropertyItem](#propertyitem) +- [OutputFlags](#outputflags) +- [Output](#output) +- [SceneItemTransform](#sceneitemtransform) +- [SceneItem](#sceneitem) +- [Scene](#scene) +- [Transition](#transition) +- [Monitor](#monitor) + +## Filter + +Source filter information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| filterEnabled | Boolean | Whether the filter is enabled | +| filterIndex | Number | Index position of the filter | +| filterKind | String | Kind of the filter | +| filterName | String | Name of the filter | +| filterSettings | Object | Settings object associated with the filter | + +--- + +## Input + +Input source information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| inputUuid | String | UUID of the input | +| inputKind | String | Kind of the input | +| unversionedInputKind | String | Unversioned kind of the input (can be used across OBS versions) | +| inputKindCaps | Number | Capabilities of the input kind | + +--- + +## PropertyItem + +Property list item information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| itemName | String | Name of the item | +| itemEnabled | Boolean | Whether the item is enabled | +| itemValue | Any | Value of the item | + +--- + +## OutputFlags + +Output flags information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| OBS_OUTPUT_AUDIO | Boolean | Output has audio capability | +| OBS_OUTPUT_VIDEO | Boolean | Output has video capability | +| OBS_OUTPUT_ENCODED | Boolean | Output uses encoded data | +| OBS_OUTPUT_MULTI_TRACK | Boolean | Output supports multi-track audio | +| OBS_OUTPUT_SERVICE | Boolean | Output is bound to a service | + +--- + +## Output + +Output information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputName | String | Name of the output | +| outputKind | String | Kind of the output | +| outputWidth | Number | Width of the output in pixels | +| outputHeight | Number | Height of the output in pixels | +| outputActive | Boolean | Whether the output is active | +| outputFlags | OutputFlags | Output capability flags | + +--- + +## SceneItemTransform + +Scene item transform information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sourceWidth | Number | Width of the source associated with the scene item | +| sourceHeight | Number | Height of the source associated with the scene item | +| positionX | Number | The x position of the scene item from the left | +| positionY | Number | The y position of the scene item from the top | +| rotation | Number | The clockwise rotation of the scene item in degrees around the point of alignment | +| scaleX | Number | The x-scale factor of the scene item | +| scaleY | Number | The y-scale factor of the scene item | +| width | Number | The rendered width of the scene item | +| height | Number | The rendered height of the scene item | +| alignment | Number | The alignment of the scene item | +| boundsType | String | Type of bounding box | +| boundsAlignment | Number | Alignment of the bounding box | +| boundsWidth | Number | Width of the bounding box | +| boundsHeight | Number | Height of the bounding box | +| cropLeft | Number | The number of pixels cropped off the left of the source before scaling | +| cropRight | Number | The number of pixels cropped off the right of the source before scaling | +| cropTop | Number | The number of pixels cropped off the top of the source before scaling | +| cropBottom | Number | The number of pixels cropped off the bottom of the source before scaling | +| cropToBounds | Boolean | Whether the crop is to the bounding box or to the scene item bounding box | + +--- + +## SceneItem + +Scene item information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemId | Number | Numeric ID of the scene item | +| sceneItemIndex | Number | Index position of the scene item | +| sceneItemEnabled | Boolean | Whether the scene item is enabled | +| sceneItemLocked | Boolean | Whether the scene item is locked | +| sceneItemTransform | SceneItemTransform | Transform information of the scene item | +| sceneItemBlendMode | Number | Blend mode of the scene item | +| sourceName | String | Name of the source associated with the scene item | +| sourceUuid | String | UUID of the source associated with the scene item | +| sourceType | Number | Type of the source | +| inputKind | String | The kind of input source (null if not an input) | +| isGroup | Boolean | Whether the source is a group (null if not a scene) | + +--- + +## Scene + +Scene information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene | +| sceneUuid | String | UUID of the scene | +| sceneIndex | Number | Index of the scene | + +--- + +## Transition + +Scene transition information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| transitionName | String | Name of the transition | +| transitionUuid | String | UUID of the transition | +| transitionKind | String | Kind of the transition | +| transitionFixed | Boolean | Whether the transition has fixed duration | +| transitionConfigurable | Boolean | Whether the transition is configurable | + +--- + +## Monitor + +Monitor/display information. + + +**Type Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| monitorName | String | Name and index of the monitor | +| monitorIndex | Number | Index of the monitor | +| monitorWidth | Number | Width of the monitor in pixels | +| monitorHeight | Number | Height of the monitor in pixels | +| monitorPositionX | Number | X position of the monitor | +| monitorPositionY | Number | Y position of the monitor | + # Enums These are enumeration declarations, which are referenced throughout obs-websocket's protocol. @@ -3344,7 +3547,7 @@ Gets an array of all scenes in OBS. | currentProgramSceneUuid | String | Current program scene UUID. Can be `null` if internal state desync | | currentPreviewSceneName | String | Current preview scene name. `null` if not in studio mode | | currentPreviewSceneUuid | String | Current preview scene UUID. `null` if not in studio mode | -| scenes | Array<Object> | Array of scenes | +| scenes | Array<Scene> | Array of scenes | --- @@ -3566,7 +3769,7 @@ Gets an array of all inputs in OBS. | Name | Type | Description | | ---- | :---: | ----------- | -| inputs | Array<Object> | Array of inputs | +| inputs | Array<Input> | Array of inputs | --- @@ -4153,7 +4356,7 @@ Note: Use this in cases where an input provides a dynamic, selectable list of it | Name | Type | Description | | ---- | :---: | ----------- | -| propertyItems | Array<Object> | Array of items in the list property | +| propertyItems | Array<PropertyItem> | Array of items in the list property | --- @@ -4214,7 +4417,7 @@ Gets an array of all scene transitions in OBS. | currentSceneTransitionName | String | Name of the current scene transition. Can be null | | currentSceneTransitionUuid | String | UUID of the current scene transition. Can be null | | currentSceneTransitionKind | String | Kind of the current scene transition. Can be null | -| transitions | Array<Object> | Array of transitions | +| transitions | Array<Transition> | Array of transitions | --- @@ -4375,7 +4578,7 @@ Gets an array of all of a source's filters. | Name | Type | Description | | ---- | :---: | ----------- | -| filters | Array<Object> | Array of filters | +| filters | Array<Filter> | Array of filters | --- @@ -4564,7 +4767,7 @@ Scenes only | Name | Type | Description | | ---- | :---: | ----------- | -| sceneItems | Array<Object> | Array of scene items in the scene | +| sceneItems | Array<SceneItem> | Array of scene items in the scene | --- @@ -4591,7 +4794,7 @@ Groups only | Name | Type | Description | | ---- | :---: | ----------- | -| sceneItems | Array<Object> | Array of scene items in the group | +| sceneItems | Array<SceneItem> | Array of scene items in the group | --- @@ -4745,7 +4948,7 @@ Scenes and Groups | Name | Type | Description | | ---- | :---: | ----------- | -| sceneItemTransform | Object | Object containing scene item transform info | +| sceneItemTransform | SceneItemTransform | Object containing scene item transform info | --- @@ -4764,7 +4967,7 @@ Sets the transform and crop info of a scene item. | ?sceneName | String | Name of the scene the item is in | None | Unknown | | ?sceneUuid | String | UUID of the scene the item is in | None | Unknown | | sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | -| sceneItemTransform | Object | Object containing scene item transform info to update | None | N/A | +| sceneItemTransform | SceneItemTransform | Object containing scene item transform info to update | None | N/A | --- @@ -5110,7 +5313,7 @@ Gets the list of available outputs. | Name | Type | Description | | ---- | :---: | ----------- | -| outputs | Array<Object> | Array of outputs | +| outputs | Array<Output> | Array of outputs | --- @@ -5620,7 +5823,7 @@ Gets a list of connected monitors and information about them. | Name | Type | Description | | ---- | :---: | ----------- | -| monitors | Array<Object> | a list of detected monitors with some information | +| monitors | Array<Monitor> | a list of detected monitors with some information | --- diff --git a/src/requesthandler/RequestHandler_Filters.cpp b/src/requesthandler/RequestHandler_Filters.cpp index bbdaac5a..3b7d1a43 100644 --- a/src/requesthandler/RequestHandler_Filters.cpp +++ b/src/requesthandler/RequestHandler_Filters.cpp @@ -19,6 +19,18 @@ with this program. If not, see #include "RequestHandler.h" +/** + * Source filter information. + * + * @typedef Filter + * @field filterEnabled | Boolean | Whether the filter is enabled + * @field filterIndex | Number | Index position of the filter + * @field filterKind | String | Kind of the filter + * @field filterName | String | Name of the filter + * @field filterSettings | Object | Settings object associated with the filter + * @api types + */ + /** * Gets an array of all available source filter kinds. * @@ -46,7 +58,7 @@ RequestResult RequestHandler::GetSourceFilterKindList(const Request &) * @requestField ?sourceName | String | Name of the source * @requestField ?sourceUuid | String | UUID of the source * - * @responseField filters | Array | Array of filters + * @responseField filters | Array | Array of filters * * @requestType GetSourceFilterList * @complexity 2 diff --git a/src/requesthandler/RequestHandler_Inputs.cpp b/src/requesthandler/RequestHandler_Inputs.cpp index bdd88177..5744bc6c 100644 --- a/src/requesthandler/RequestHandler_Inputs.cpp +++ b/src/requesthandler/RequestHandler_Inputs.cpp @@ -19,12 +19,34 @@ with this program. If not, see #include "RequestHandler.h" +/** + * Input source information. + * + * @typedef Input + * @field inputName | String | Name of the input + * @field inputUuid | String | UUID of the input + * @field inputKind | String | Kind of the input + * @field unversionedInputKind | String | Unversioned kind of the input (can be used across OBS versions) + * @field inputKindCaps | Number | Capabilities of the input kind + * @api types + */ + +/** + * Property list item information. + * + * @typedef PropertyItem + * @field itemName | String | Name of the item + * @field itemEnabled | Boolean | Whether the item is enabled + * @field itemValue | Any | Value of the item + * @api types + */ + /** * Gets an array of all inputs in OBS. * * @requestField ?inputKind | String | Restrict the array to only inputs of the specified kind | All kinds included * - * @responseField inputs | Array | Array of inputs + * @responseField inputs | Array | Array of inputs * * @requestType GetInputList * @complexity 2 @@ -1036,7 +1058,7 @@ RequestResult RequestHandler::SetInputDeinterlaceFieldOrder(const Request &reque * @requestField ?inputUuid | String | UUID of the input * @requestField propertyName | String | Name of the list property to get the items of * - * @responseField propertyItems | Array | Array of items in the list property + * @responseField propertyItems | Array | Array of items in the list property * * @requestType GetInputPropertiesListPropertyItems * @complexity 4 diff --git a/src/requesthandler/RequestHandler_Outputs.cpp b/src/requesthandler/RequestHandler_Outputs.cpp index d2b0526b..ee4c4339 100644 --- a/src/requesthandler/RequestHandler_Outputs.cpp +++ b/src/requesthandler/RequestHandler_Outputs.cpp @@ -31,6 +31,31 @@ static bool ReplayBufferAvailable() return output != nullptr; } +/** + * Output flags information. + * + * @typedef OutputFlags + * @field OBS_OUTPUT_AUDIO | Boolean | Output has audio capability + * @field OBS_OUTPUT_VIDEO | Boolean | Output has video capability + * @field OBS_OUTPUT_ENCODED | Boolean | Output uses encoded data + * @field OBS_OUTPUT_MULTI_TRACK | Boolean | Output supports multi-track audio + * @field OBS_OUTPUT_SERVICE | Boolean | Output is bound to a service + * @api types + */ + +/** + * Output information. + * + * @typedef Output + * @field outputName | String | Name of the output + * @field outputKind | String | Kind of the output + * @field outputWidth | Number | Width of the output in pixels + * @field outputHeight | Number | Height of the output in pixels + * @field outputActive | Boolean | Whether the output is active + * @field outputFlags | OutputFlags | Output capability flags + * @api types + */ + /** * Gets the status of the virtualcam output. * @@ -276,7 +301,7 @@ RequestResult RequestHandler::GetLastReplayBufferReplay(const Request &) /** * Gets the list of available outputs. * - * @responseField outputs | Array | Array of outputs + * @responseField outputs | Array | Array of outputs * * @requestType GetOutputList * @complexity 4 diff --git a/src/requesthandler/RequestHandler_SceneItems.cpp b/src/requesthandler/RequestHandler_SceneItems.cpp index 146fb654..3959418c 100644 --- a/src/requesthandler/RequestHandler_SceneItems.cpp +++ b/src/requesthandler/RequestHandler_SceneItems.cpp @@ -19,6 +19,50 @@ with this program. If not, see #include "RequestHandler.h" +/** + * Scene item transform information. + * + * @typedef SceneItemTransform + * @field sourceWidth | Number | Width of the source associated with the scene item + * @field sourceHeight | Number | Height of the source associated with the scene item + * @field positionX | Number | The x position of the scene item from the left + * @field positionY | Number | The y position of the scene item from the top + * @field rotation | Number | The clockwise rotation of the scene item in degrees around the point of alignment + * @field scaleX | Number | The x-scale factor of the scene item + * @field scaleY | Number | The y-scale factor of the scene item + * @field width | Number | The rendered width of the scene item + * @field height | Number | The rendered height of the scene item + * @field alignment | Number | The alignment of the scene item + * @field boundsType | String | Type of bounding box + * @field boundsAlignment | Number | Alignment of the bounding box + * @field boundsWidth | Number | Width of the bounding box + * @field boundsHeight | Number | Height of the bounding box + * @field cropLeft | Number | The number of pixels cropped off the left of the source before scaling + * @field cropRight | Number | The number of pixels cropped off the right of the source before scaling + * @field cropTop | Number | The number of pixels cropped off the top of the source before scaling + * @field cropBottom | Number | The number of pixels cropped off the bottom of the source before scaling + * @field cropToBounds | Boolean | Whether the crop is to the bounding box or to the scene item bounding box + * @api types + */ + +/** + * Scene item information. + * + * @typedef SceneItem + * @field sceneItemId | Number | Numeric ID of the scene item + * @field sceneItemIndex | Number | Index position of the scene item + * @field sceneItemEnabled | Boolean | Whether the scene item is enabled + * @field sceneItemLocked | Boolean | Whether the scene item is locked + * @field sceneItemTransform | SceneItemTransform | Transform information of the scene item + * @field sceneItemBlendMode | Number | Blend mode of the scene item + * @field sourceName | String | Name of the source associated with the scene item + * @field sourceUuid | String | UUID of the source associated with the scene item + * @field sourceType | Number | Type of the source + * @field inputKind | String | The kind of input source (null if not an input) + * @field isGroup | Boolean | Whether the source is a group (null if not a scene) + * @api types + */ + /** * Gets a list of all scene items in a scene. * @@ -27,7 +71,7 @@ with this program. If not, see * @requestField ?sceneName | String | Name of the scene to get the items of * @requestField ?sceneUuid | String | UUID of the scene to get the items of * - * @responseField sceneItems | Array | Array of scene items in the scene + * @responseField sceneItems | Array | Array of scene items in the scene * * @requestType GetSceneItemList * @complexity 3 @@ -60,7 +104,7 @@ RequestResult RequestHandler::GetSceneItemList(const Request &request) * @requestField ?sceneName | String | Name of the group to get the items of * @requestField ?sceneUuid | String | UUID of the group to get the items of * - * @responseField sceneItems | Array | Array of scene items in the group + * @responseField sceneItems | Array | Array of scene items in the group * * @requestType GetGroupSceneItemList * @complexity 3 @@ -334,7 +378,7 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request &request) * @requestField ?sceneUuid | String | UUID of the scene the item is in * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 * - * @responseField sceneItemTransform | Object | Object containing scene item transform info + * @responseField sceneItemTransform | SceneItemTransform | Object containing scene item transform info * * @requestType GetSceneItemTransform * @complexity 3 @@ -364,7 +408,7 @@ RequestResult RequestHandler::GetSceneItemTransform(const Request &request) * @requestField ?sceneName | String | Name of the scene the item is in * @requestField ?sceneUuid | String | UUID of the scene the item is in * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 - * @requestField sceneItemTransform | Object | Object containing scene item transform info to update + * @requestField sceneItemTransform | SceneItemTransform | Object containing scene item transform info to update * * @requestType SetSceneItemTransform * @complexity 3 diff --git a/src/requesthandler/RequestHandler_Scenes.cpp b/src/requesthandler/RequestHandler_Scenes.cpp index 47ede5dd..94113c0c 100644 --- a/src/requesthandler/RequestHandler_Scenes.cpp +++ b/src/requesthandler/RequestHandler_Scenes.cpp @@ -19,6 +19,16 @@ with this program. If not, see #include "RequestHandler.h" +/** + * Scene information. + * + * @typedef Scene + * @field sceneName | String | Name of the scene + * @field sceneUuid | String | UUID of the scene + * @field sceneIndex | Number | Index of the scene + * @api types + */ + /** * Gets an array of all scenes in OBS. * @@ -26,7 +36,7 @@ with this program. If not, see * @responseField currentProgramSceneUuid | String | Current program scene UUID. Can be `null` if internal state desync * @responseField currentPreviewSceneName | String | Current preview scene name. `null` if not in studio mode * @responseField currentPreviewSceneUuid | String | Current preview scene UUID. `null` if not in studio mode - * @responseField scenes | Array | Array of scenes + * @responseField scenes | Array | Array of scenes * * @requestType GetSceneList * @complexity 2 diff --git a/src/requesthandler/RequestHandler_Transitions.cpp b/src/requesthandler/RequestHandler_Transitions.cpp index e2d87c5c..d6670ab9 100644 --- a/src/requesthandler/RequestHandler_Transitions.cpp +++ b/src/requesthandler/RequestHandler_Transitions.cpp @@ -21,6 +21,18 @@ with this program. If not, see #include "RequestHandler.h" +/** + * Scene transition information. + * + * @typedef Transition + * @field transitionName | String | Name of the transition + * @field transitionUuid | String | UUID of the transition + * @field transitionKind | String | Kind of the transition + * @field transitionFixed | Boolean | Whether the transition has fixed duration + * @field transitionConfigurable | Boolean | Whether the transition is configurable + * @api types + */ + /** * Gets an array of all available transition kinds. * @@ -48,7 +60,7 @@ RequestResult RequestHandler::GetTransitionKindList(const Request &) * @responseField currentSceneTransitionName | String | Name of the current scene transition. Can be null * @responseField currentSceneTransitionUuid | String | UUID of the current scene transition. Can be null * @responseField currentSceneTransitionKind | String | Kind of the current scene transition. Can be null - * @responseField transitions | Array | Array of transitions + * @responseField transitions | Array | Array of transitions * * @requestType GetSceneTransitionList * @complexity 3 diff --git a/src/requesthandler/RequestHandler_Ui.cpp b/src/requesthandler/RequestHandler_Ui.cpp index 29896bda..687485b4 100644 --- a/src/requesthandler/RequestHandler_Ui.cpp +++ b/src/requesthandler/RequestHandler_Ui.cpp @@ -24,6 +24,19 @@ with this program. If not, see #include "RequestHandler.h" +/** + * Monitor/display information. + * + * @typedef Monitor + * @field monitorName | String | Name and index of the monitor + * @field monitorIndex | Number | Index of the monitor + * @field monitorWidth | Number | Width of the monitor in pixels + * @field monitorHeight | Number | Height of the monitor in pixels + * @field monitorPositionX | Number | X position of the monitor + * @field monitorPositionY | Number | Y position of the monitor + * @api types + */ + /** * Gets whether studio is enabled. * @@ -164,7 +177,7 @@ RequestResult RequestHandler::OpenInputInteractDialog(const Request &request) /** * Gets a list of connected monitors and information about them. * - * @responseField monitors | Array | a list of detected monitors with some information + * @responseField monitors | Array | a list of detected monitors with some information * * @requestType GetMonitorList * @complexity 2