obws/responses/mod.rs
1//! All responses that can be received from the API.
2
3pub mod canvases;
4pub mod config;
5pub mod filters;
6pub mod general;
7pub(crate) mod hotkeys;
8pub(crate) mod ids;
9pub mod inputs;
10pub mod media_inputs;
11pub mod outputs;
12pub mod profiles;
13pub mod recording;
14pub(crate) mod replay_buffer;
15pub mod scene_collections;
16pub mod scene_items;
17pub mod scenes;
18pub mod sources;
19pub mod streaming;
20pub mod transitions;
21pub mod ui;
22pub(crate) mod virtual_cam;
23
24use derive_more::derive::TryFrom;
25use serde::{Deserialize, Deserializer, Serialize, de};
26
27#[derive(Debug)]
28pub(crate) enum ServerMessage {
29 /// First message sent from the server immediately on client connection. Contains
30 /// authentication information if authentication is required. Also contains RPC version for
31 /// version negotiation.
32 Hello(Hello),
33 /// The identify request was received and validated, and the connection is now ready for normal
34 /// operation.
35 Identified(Identified),
36 /// An event coming from OBS has occurred. For example scene switched, source muted.
37 #[cfg(feature = "events")]
38 Event(crate::events::Event),
39 #[cfg(not(feature = "events"))]
40 Event,
41 /// `obs-websocket` is responding to a request coming from a client.
42 RequestResponse(RequestResponse),
43 /// `obs-websocket` is responding to a request batch coming from the client.
44 #[allow(dead_code)]
45 RequestBatchResponse(RequestBatchResponse),
46}
47
48impl<'de> Deserialize<'de> for ServerMessage {
49 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
50 where
51 D: Deserializer<'de>,
52 {
53 #[derive(Deserialize)]
54 struct RawServerMessage {
55 #[serde(rename = "op")]
56 op_code: OpCode,
57 #[serde(rename = "d")]
58 data: serde_json::Value,
59 }
60
61 #[derive(Deserialize, TryFrom)]
62 #[serde(try_from = "u8")]
63 #[try_from(repr)]
64 #[repr(u8)]
65 enum OpCode {
66 /// The initial message sent by obs-websocket to newly connected clients.
67 Hello = 0,
68 /// The response sent by obs-websocket to a client after it has successfully identified
69 /// with obs-websocket.
70 Identified = 2,
71 /// The message sent by obs-websocket containing an event payload.
72 Event = 5,
73 /// The message sent by obs-websocket in response to a particular request from a
74 /// client.
75 RequestResponse = 7,
76 /// The message sent by obs-websocket in response to a particular batch of requests
77 /// from a client.
78 RequestBatchResponse = 9,
79 }
80
81 let raw = RawServerMessage::deserialize(deserializer)?;
82
83 Ok(match raw.op_code {
84 OpCode::Hello => {
85 ServerMessage::Hello(serde_json::from_value(raw.data).map_err(de::Error::custom)?)
86 }
87 OpCode::Identified => ServerMessage::Identified(
88 serde_json::from_value(raw.data).map_err(de::Error::custom)?,
89 ),
90 OpCode::Event => {
91 #[cfg(feature = "events")]
92 {
93 ServerMessage::Event(
94 serde_json::from_value(raw.data).map_err(de::Error::custom)?,
95 )
96 }
97 #[cfg(not(feature = "events"))]
98 {
99 ServerMessage::Event
100 }
101 }
102 OpCode::RequestResponse => ServerMessage::RequestResponse(
103 serde_json::from_value(raw.data).map_err(de::Error::custom)?,
104 ),
105 OpCode::RequestBatchResponse => ServerMessage::RequestBatchResponse(
106 serde_json::from_value(raw.data).map_err(de::Error::custom)?,
107 ),
108 })
109 }
110}
111
112/// First message sent from the server immediately on client connection. Contains authentication
113/// information if authentication is required. Also contains RPC version for version negotiation.
114#[derive(Debug, Deserialize)]
115pub(crate) struct Hello {
116 #[allow(dead_code)]
117 #[serde(rename = "obsWebSocketVersion")]
118 pub obs_web_socket_version: semver::Version,
119 /// version number which gets incremented on each **breaking change** to the obs-websocket
120 /// protocol. Its usage in this context is to provide the current RPC version that the server
121 /// would like to use.
122 #[serde(rename = "rpcVersion")]
123 pub rpc_version: u32,
124 #[serde(rename = "authentication")]
125 pub authentication: Option<Authentication>,
126}
127
128/// The identify request was received and validated, and the connection is now ready for normal
129/// operation.
130#[derive(Debug, Deserialize)]
131pub(crate) struct Identified {
132 /// The RPC (remote procedure call) version to be used.
133 #[serde(rename = "negotiatedRpcVersion")]
134 pub negotiated_rpc_version: u32,
135}
136
137/// `obs-websocket` is responding to a request coming from a client.
138#[derive(Debug, Deserialize)]
139pub(crate) struct RequestResponse {
140 #[allow(dead_code)]
141 #[serde(rename = "requestType")]
142 pub r#type: String,
143 #[serde(rename = "requestId")]
144 pub id: String,
145 #[serde(rename = "requestStatus")]
146 pub status: Status,
147 #[serde(rename = "responseData", default)]
148 pub data: serde_json::Value,
149}
150
151#[derive(Debug, Deserialize)]
152pub(crate) struct RequestBatchResponse {
153 #[allow(dead_code)]
154 #[serde(rename = "requestId")]
155 pub id: String,
156 #[allow(dead_code)]
157 pub results: Vec<serde_json::Value>,
158}
159
160#[derive(Debug, Deserialize)]
161pub(crate) struct Authentication {
162 pub challenge: String,
163 pub salt: String,
164}
165
166#[derive(Debug, Deserialize)]
167pub(crate) struct Status {
168 /// Is true if the request resulted in [`StatusCode::Success`]. False if otherwise.
169 pub result: bool,
170 pub code: StatusCode,
171 /// May be provided by the server on errors to offer further details on why a request failed.
172 pub comment: Option<String>,
173}
174
175/// The status code gives information about the result of a request. It gives further insight into
176/// what went wrong, if a request failed.
177#[derive(
178 Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize, TryFrom,
179)]
180#[serde(into = "u16", try_from = "u16")]
181#[try_from(repr)]
182#[repr(u16)]
183#[non_exhaustive]
184pub enum StatusCode {
185 /// Unknown status, should never be used.
186 Unknown = 0,
187
188 /// For internal use to signify a successful field check.
189 NoError = 10,
190
191 /// The request has succeeded.
192 Success = 100,
193
194 /// The `requestType` field is missing from the request data.
195 MissingRequestType = 203,
196 /// The request type is invalid or does not exist.
197 UnknownRequestType = 204,
198 /// Generic error code.
199 ///
200 /// **Note:** A comment is required to be provided by obs-websocket.
201 GenericError = 205,
202 /// The request batch execution type is not supported.
203 UnsupportedRequestBatchExecutionType = 206,
204 /// The server is not ready to handle the request.
205 ///
206 /// **Note:** This usually occurs during OBS scene collection change or exit. Requests may be
207 /// tried again after a delay if this code is given.
208 NotReady = 207,
209
210 /// A required request field is missing.
211 MissingRequestField = 300,
212 /// The request does not have a valid `requestData` object.
213 MissingRequestData = 301,
214
215 /// Generic invalid request field message.
216 ///
217 /// **Note:** A comment is required to be provided by obs-websocket.
218 InvalidRequestField = 400,
219 /// A request field has the wrong data type.
220 InvalidRequestFieldType = 401,
221 /// A request field (number) is outside the allowed range.
222 RequestFieldOutOfRange = 402,
223 /// A request field (string or array) is empty and cannot be.
224 RequestFieldEmpty = 403,
225 /// There are too many request fields (For example a request takes two optional fields, where
226 /// only one is allowed at a time).
227 TooManyRequestFields = 404,
228
229 /// An output is running and cannot be in order to perform the request.
230 OutputRunning = 500,
231 /// An output is not running and should be.
232 OutputNotRunning = 501,
233 /// An output is paused and should not be.
234 OutputPaused = 502,
235 /// An output is not paused and should be.
236 OutputNotPaused = 503,
237 /// An output is disabled and should not be.
238 OutputDisabled = 504,
239 /// Studio mode is active and cannot be.
240 StudioModeActive = 505,
241 /// Studio mode is not active and should be.
242 StudioModeNotActive = 506,
243
244 /// The resource was not found.
245 ///
246 /// **Note:** Resources are any kind of object in obs-websocket, like inputs, profiles,
247 /// outputs, etc.
248 ResourceNotFound = 600,
249 /// The resource already exists.
250 ResourceAlreadyExists = 601,
251 /// The type of resource found is invalid.
252 InvalidResourceType = 602,
253 /// There are not enough instances of the resource in order to perform the request.
254 NotEnoughResources = 603,
255 /// The state of the resource is invalid. For example, if the resource is blocked from being
256 /// accessed.
257 InvalidResourceState = 604,
258 /// The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind.
259 InvalidInputKind = 605,
260 /// The resource does not support being configured.
261 ///
262 /// This is particularly relevant to transitions, where they do not always have changeable
263 /// settings.
264 ResourceNotConfigurable = 606,
265 /// The specified filter had the wrong kind.
266 InvalidFilterKind = 607,
267
268 /// Creating the resource failed.
269 ResourceCreationFailed = 700,
270 /// Performing an action on the resource failed.
271 ResourceActionFailed = 701,
272 /// Processing the request failed unexpectedly.
273 ///
274 /// **Note:** A comment is required to be provided by obs-websocket.
275 RequestProcessingFailed = 702,
276 /// The combination of request fields cannot be used to perform an action.
277 CannotAct = 703,
278}
279
280impl From<StatusCode> for u16 {
281 fn from(value: StatusCode) -> Self {
282 value as Self
283 }
284}
285
286/// Additional close codes, defined by `obs-websocket`. These can be used to further pin down the
287/// details of why the web-socket connection was closed.
288#[derive(
289 Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize, TryFrom,
290)]
291#[serde(into = "u16", try_from = "u16")]
292#[try_from(repr)]
293#[repr(u16)]
294#[non_exhaustive]
295pub enum WebSocketCloseCode {
296 /// Unknown reason, should never be used.
297 UnknownReason = 4000,
298 /// The server was unable to decode the incoming web-socket message.
299 MessageDecodeError = 4002,
300 /// A data field is required but missing from the payload.
301 MissingDataField = 4003,
302 /// A data field's value type is invalid.
303 InvalidDataFieldType = 4004,
304 /// A data field's value is invalid.
305 InvalidDataFieldValue = 4005,
306 /// The specified `op` was invalid or missing.
307 UnknownOpCode = 4006,
308 /// The client sent a web-socket message without first sending `Identify` message.
309 NotIdentified = 4007,
310 /// The client sent an `Identify` message while already identified.
311 ///
312 /// **Note:** Once a client has identified, only `Reidentify` may be used to change session
313 /// parameters.
314 AlreadyIdentified = 4008,
315 /// The authentication attempt (via `Identify`) failed.
316 AuthenticationFailed = 4009,
317 /// The server detected the usage of an old version of the obs-websocket RPC protocol.
318 UnsupportedRpcVersion = 4010,
319 /// The web-socket session has been invalidated by the obs-websocket server.
320 ///
321 /// **Note:** This is the code used by the `Kick` button in the UI Session List. If you receive
322 /// this code, you must not automatically reconnect.
323 SessionInvalidated = 4011,
324 /// A requested feature is not supported due to hardware/software limitations.
325 UnsupportedFeature = 4012,
326}
327
328impl From<WebSocketCloseCode> for u16 {
329 fn from(value: WebSocketCloseCode) -> Self {
330 value as Self
331 }
332}