Compare commits

...

5 Commits

Author SHA1 Message Date
Dominik Nakamura 69e9a3b2be
Bump up version number to 0.12.0 4 weeks ago
Dominik Nakamura 27f760033d
feat: allow setting a connection timeout 4 weeks ago
Dominik Nakamura bfdf56b190
chore(deps): update dependencies 4 weeks ago
Dominik Nakamura 0a4f3ad772
refactor: remove unused error variant 4 weeks ago
Dominik Nakamura a8ad9db37f
feat: add features from obs-websocket v5.4 4 weeks ago

@ -10,11 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
<!-- next-header --> <!-- next-header -->
## [Unreleased] - ReleaseDate ## [Unreleased] - ReleaseDate
## [0.12.0] - 2024-05-04
### Added ### Added
- New features from obs-websocket v5.3.0 - New features from obs-websocket [v5.3.0](https://github.com/obsproject/obs-websocket/releases/tag/5.3.0)
- New `set_record_directory` command, that allows to modify the output directory for recordings. - New `set_record_directory` command, that allows to modify the output directory for recordings.
- New `NotReady` status code, that signals the obs-websocket server is not ready yet to accept any commands. - New `NotReady` status code, that signals the obs-websocket server is not ready yet to accept any commands.
- New features from obs-websocket [v5.4.0](https://github.com/obsproject/obs-websocket/releases/tag/5.4.0)
- **BREAKING CHANGE:** Support for UUIDs to identify sources, inputs, scenes and transitions. This includes various breaking changes around those fields to make it impossible to misuse them in requests (basically using an enum to only use the name _or_ UUID in a request but not both).
- New `list_kinds` command for filters.
- New `source` command for scene items.
- New `InputSettingsChanged` and `SourceFilterSettingsChanged` event.
- **BREAKING CHANGE:** Extend the connection config with a `connection_timeout` setting that allows to cancel the connection attempt after a set duration. The `Client::connect` method now defaults to a _30 second_ timeout.
### Removed
- **BREAKING CHANGE:** Removed the `Error::NoPassword` variant, which was a relic from the previous v4.x API of obs-websocket. The new API doesn't provide any way to tell whether the password is the cause of an authentication error. However, the `WebSocketCloseCode::AuthenticationFailed` is the closest to identifying this error.
## [0.11.5] - 2023-09-04 ## [0.11.5] - 2023-09-04
@ -260,7 +272,8 @@ is currently in progress on the `v5-api` branch and the release is expected to b
- Initial release. - Initial release.
<!-- next-url --> <!-- next-url -->
[Unreleased]: https://github.com/dnaka91/obws/compare/v0.11.5...HEAD [Unreleased]: https://github.com/dnaka91/obws/compare/v0.12.0...HEAD
[0.12.0]: https://github.com/dnaka91/obws/compare/v0.11.5...v0.12.0
[0.11.5]: https://github.com/dnaka91/obws/compare/v0.11.4...v0.11.5 [0.11.5]: https://github.com/dnaka91/obws/compare/v0.11.4...v0.11.5
[0.11.4]: https://github.com/dnaka91/obws/compare/v0.11.3...v0.11.4 [0.11.4]: https://github.com/dnaka91/obws/compare/v0.11.3...v0.11.4
[0.11.3]: https://github.com/dnaka91/obws/compare/v0.11.2...v0.11.3 [0.11.3]: https://github.com/dnaka91/obws/compare/v0.11.2...v0.11.3

@ -1,6 +1,6 @@
[package] [package]
name = "obws" name = "obws"
version = "0.11.5" version = "0.12.0"
authors = ["Dominik Nakamura <dnaka91@gmail.com>"] authors = ["Dominik Nakamura <dnaka91@gmail.com>"]
edition = "2021" edition = "2021"
rust-version = "1.68" rust-version = "1.68"
@ -18,27 +18,28 @@ features = ["events", "tls"]
[dependencies] [dependencies]
async-stream = { version = "0.3.5", optional = true } async-stream = { version = "0.3.5", optional = true }
base64 = "0.21.7" base64 = "0.22.1"
bitflags = { version = "2.4.2", features = ["serde"] } bitflags = { version = "2.5.0", features = ["serde"] }
futures-util = { version = "0.3.30", features = ["sink"] } futures-util = { version = "0.3.30", features = ["sink"] }
rgb = { version = "0.8.37", default-features = false } rgb = { version = "0.8.37", default-features = false }
semver = { version = "1.0.21", features = ["serde"] } semver = { version = "1.0.22", features = ["serde"] }
serde = { version = "1.0.195", features = ["derive"] } serde = { version = "1.0.200", features = ["derive"] }
serde_json = "1.0.111" serde_json = "1.0.116"
serde_repr = "0.1.18" serde_repr = "0.1.19"
serde_with = "3.4.0" serde_with = "3.8.1"
sha2 = "0.10.8" sha2 = "0.10.8"
thiserror = "1.0.56" thiserror = "1.0.59"
time = "0.3.31" time = "0.3.36"
tokio = { version = "1.35.1", features = ["net", "rt", "sync", "time"] } tokio = { version = "1.37.0", features = ["net", "rt", "sync", "time"] }
tokio-tungstenite = "0.21.0" tokio-tungstenite = "0.21.0"
tracing = "0.1.40" tracing = "0.1.40"
uuid = { version = "1.8.0", features = ["serde"] }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.79" anyhow = "1.0.82"
dotenvy = "0.15.7" dotenvy = "0.15.7"
serde_test = "1.0.176" serde_test = "1.0.176"
tokio = { version = "1.35.1", features = ["fs", "macros", "rt-multi-thread", "time"] } tokio = { version = "1.37.0", features = ["fs", "macros", "rt-multi-thread", "time"] }
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
[features] [features]

@ -20,12 +20,12 @@ Remote control OBS with the [obs-websocket] plugin from Rust 🦀.
## Usage ## Usage
Add `obws` to your project with `cargo add obws@0.11.5` or add it manually to your Add `obws` to your project with `cargo add obws@0.12.0` or add it manually to your
`Cargo.toml`: `Cargo.toml`:
```toml ```toml
[dependencies] [dependencies]
obws = "0.11.5" obws = "0.12.0"
``` ```
In addition, you will need to use the latest [tokio](https://tokio.rs) runtime to use this library In addition, you will need to use the latest [tokio](https://tokio.rs) runtime to use this library

@ -1,9 +1,19 @@
[advisories]
version = 2
[licenses] [licenses]
version = 2
allow = [ allow = [
"Apache-2.0",
"BSD-3-Clause",
"ISC",
"MIT",
"MPL-2.0", "MPL-2.0",
"OpenSSL", "OpenSSL",
] ]
allow-osi-fsf-free = "both" exceptions = [
{ allow = ["Unicode-DFS-2016"], name = "unicode-ident" },
]
[[licenses.clarify]] [[licenses.clarify]]
name = "ring" name = "ring"
@ -13,7 +23,7 @@ license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 }, { path = "LICENSE", hash = 0xbd0eed23 },
] ]
[[licenses.exceptions]] [bans]
allow = ["Unicode-DFS-2016"] skip-tree = [
name = "unicode-ident" { name = "windows-sys", version = "0.48", depth = 3 },
version = "*" ]

@ -17,7 +17,7 @@ async fn main() -> Result<()> {
for scene in scene_list.scenes.iter().cycle() { for scene in scene_list.scenes.iter().cycle() {
client client
.scenes() .scenes()
.set_current_program_scene(&scene.name) .set_current_program_scene(&*scene.name)
.await?; .await?;
tokio::time::sleep(Duration::from_secs(1)).await; tokio::time::sleep(Duration::from_secs(1)).await;
} }

@ -17,7 +17,7 @@ async fn main() -> Result<()> {
let screenshot = client let screenshot = client
.sources() .sources()
.take_screenshot(TakeScreenshot { .take_screenshot(TakeScreenshot {
source: "OBWS-TEST-Scene", source: "OBWS-TEST-Scene".into(),
width: None, width: None,
height: None, height: None,
compression_quality: None, compression_quality: None,

@ -2,9 +2,12 @@ use serde::{de::DeserializeOwned, Serialize};
use super::Client; use super::Client;
use crate::{ use crate::{
requests::filters::{ requests::{
Create, CreateInternal, Request, SetEnabled, SetIndex, SetName, SetSettings, filters::{
SetSettingsInternal, Create, CreateInternal, Request, SetEnabled, SetIndex, SetName, SetSettings,
SetSettingsInternal,
},
sources::SourceId,
}, },
responses::filters as responses, responses::filters as responses,
Error, Result, Error, Result,
@ -16,9 +19,18 @@ pub struct Filters<'a> {
} }
impl<'a> Filters<'a> { impl<'a> Filters<'a> {
/// Gets an array of all available source filter kinds.
#[doc(alias = "GetSourceFilterKindList")]
pub async fn list_kinds(&self) -> Result<Vec<String>> {
self.client
.send_message::<_, responses::FilterKinds>(Request::KindList)
.await
.map(|fk| fk.kinds)
}
/// Gets an array of all of a source's filters. /// Gets an array of all of a source's filters.
#[doc(alias = "GetSourceFilterList")] #[doc(alias = "GetSourceFilterList")]
pub async fn list(&self, source: &str) -> Result<Vec<responses::SourceFilter>> { pub async fn list(&self, source: SourceId<'_>) -> Result<Vec<responses::SourceFilter>> {
self.client self.client
.send_message::<_, responses::Filters>(Request::List { source }) .send_message::<_, responses::Filters>(Request::List { source })
.await .await
@ -61,7 +73,7 @@ impl<'a> Filters<'a> {
/// Removes a filter from a source. /// Removes a filter from a source.
#[doc(alias = "RemoveSourceFilter")] #[doc(alias = "RemoveSourceFilter")]
pub async fn remove(&self, source: &str, filter: &str) -> Result<()> { pub async fn remove(&self, source: SourceId<'_>, filter: &str) -> Result<()> {
self.client self.client
.send_message(Request::Remove { source, filter }) .send_message(Request::Remove { source, filter })
.await .await
@ -75,7 +87,7 @@ impl<'a> Filters<'a> {
/// Gets the info for a specific source filter. /// Gets the info for a specific source filter.
#[doc(alias = "GetSourceFilter")] #[doc(alias = "GetSourceFilter")]
pub async fn get(&self, source: &str, filter: &str) -> Result<responses::SourceFilter> { pub async fn get(&self, source: SourceId<'_>, filter: &str) -> Result<responses::SourceFilter> {
self.client self.client
.send_message(Request::Get { source, filter }) .send_message(Request::Get { source, filter })
.await .await

@ -22,9 +22,9 @@ impl<'a> Hotkeys<'a> {
/// Triggers a hotkey using its name. See [`Self::list`]. /// Triggers a hotkey using its name. See [`Self::list`].
#[doc(alias = "TriggerHotkeyByName")] #[doc(alias = "TriggerHotkeyByName")]
pub async fn trigger_by_name(&self, name: &str) -> Result<()> { pub async fn trigger_by_name(&self, name: &str, context: Option<&str>) -> Result<()> {
self.client self.client
.send_message(Request::TriggerByName { name }) .send_message(Request::TriggerByName { name, context })
.await .await
} }

@ -5,7 +5,7 @@ use super::Client;
use crate::{ use crate::{
common::MonitorType, common::MonitorType,
requests::inputs::{ requests::inputs::{
Create, CreateInputInternal, Request, SetSettings, SetSettingsInternal, Volume, Create, CreateInputInternal, InputId, Request, SetSettings, SetSettingsInternal, Volume,
}, },
responses::inputs as responses, responses::inputs as responses,
Error, Result, Error, Result,
@ -60,11 +60,11 @@ impl<'a> Inputs<'a> {
/// **Note:** Does not include defaults. To create the entire settings object, overlay input /// **Note:** Does not include defaults. To create the entire settings object, overlay input
/// settings over the default input settings provided by [`Inputs::default_settings`]. /// settings over the default input settings provided by [`Inputs::default_settings`].
#[doc(alias = "GetInputSettings")] #[doc(alias = "GetInputSettings")]
pub async fn settings<T>(&self, name: &str) -> Result<responses::InputSettings<T>> pub async fn settings<T>(&self, input: InputId<'_>) -> Result<responses::InputSettings<T>>
where where
T: DeserializeOwned, T: DeserializeOwned,
{ {
self.client.send_message(Request::Settings { name }).await self.client.send_message(Request::Settings { input }).await
} }
/// Sets the settings of an input. /// Sets the settings of an input.
@ -85,60 +85,60 @@ impl<'a> Inputs<'a> {
/// Gets the audio mute state of an input. /// Gets the audio mute state of an input.
#[doc(alias = "GetInputMute")] #[doc(alias = "GetInputMute")]
pub async fn muted(&self, name: &str) -> Result<bool> { pub async fn muted(&self, input: InputId<'_>) -> Result<bool> {
self.client self.client
.send_message::<_, responses::InputMuted>(Request::Muted { name }) .send_message::<_, responses::InputMuted>(Request::Muted { input })
.await .await
.map(|im| im.muted) .map(|im| im.muted)
} }
/// Sets the audio mute state of an input. /// Sets the audio mute state of an input.
#[doc(alias = "SetInputMute")] #[doc(alias = "SetInputMute")]
pub async fn set_muted(&self, name: &str, muted: bool) -> Result<()> { pub async fn set_muted(&self, input: InputId<'_>, muted: bool) -> Result<()> {
self.client self.client
.send_message(Request::SetMuted { name, muted }) .send_message(Request::SetMuted { input, muted })
.await .await
} }
/// Toggles the audio mute state of an input. /// Toggles the audio mute state of an input.
#[doc(alias = "ToggleInputMute")] #[doc(alias = "ToggleInputMute")]
pub async fn toggle_mute(&self, name: &str) -> Result<bool> { pub async fn toggle_mute(&self, input: InputId<'_>) -> Result<bool> {
self.client self.client
.send_message::<_, responses::InputMuted>(Request::ToggleMute { name }) .send_message::<_, responses::InputMuted>(Request::ToggleMute { input })
.await .await
.map(|im| im.muted) .map(|im| im.muted)
} }
/// Gets the current volume setting of an input. /// Gets the current volume setting of an input.
#[doc(alias = "GetInputVolume")] #[doc(alias = "GetInputVolume")]
pub async fn volume(&self, name: &str) -> Result<responses::InputVolume> { pub async fn volume(&self, input: InputId<'_>) -> Result<responses::InputVolume> {
self.client.send_message(Request::Volume { name }).await self.client.send_message(Request::Volume { input }).await
} }
/// Sets the volume setting of an input. /// Sets the volume setting of an input.
#[doc(alias = "SetInputVolume")] #[doc(alias = "SetInputVolume")]
pub async fn set_volume(&self, name: &str, volume: Volume) -> Result<()> { pub async fn set_volume(&self, input: InputId<'_>, volume: Volume) -> Result<()> {
self.client self.client
.send_message(Request::SetVolume { name, volume }) .send_message(Request::SetVolume { input, volume })
.await .await
} }
/// Sets the name of an input (rename). /// Sets the name of an input (rename).
#[doc(alias = "SetInputName")] #[doc(alias = "SetInputName")]
pub async fn set_name(&self, name: &str, new: &str) -> Result<()> { pub async fn set_name(&self, input: InputId<'_>, new: &str) -> Result<()> {
self.client self.client
.send_message(Request::SetName { name, new }) .send_message(Request::SetName { input, new })
.await .await
} }
/// Creates a new input, adding it as a scene item to the specified scene. /// Creates a new input, adding it as a scene item to the specified scene.
#[doc(alias = "CreateInput")] #[doc(alias = "CreateInput")]
pub async fn create<T>(&self, input: Create<'_, T>) -> Result<i64> pub async fn create<T>(&self, input: Create<'_, T>) -> Result<responses::SceneItemId>
where where
T: Serialize, T: Serialize,
{ {
self.client self.client
.send_message::<_, responses::SceneItemId>(Request::Create(CreateInputInternal { .send_message(Request::Create(CreateInputInternal {
scene: input.scene, scene: input.scene,
input: input.input, input: input.input,
kind: input.kind, kind: input.kind,
@ -151,31 +151,30 @@ impl<'a> Inputs<'a> {
enabled: input.enabled, enabled: input.enabled,
})) }))
.await .await
.map(|sii| sii.scene_item_id)
} }
/// Removes an existing input. /// Removes an existing input.
/// ///
/// **Note:** Will immediately remove all associated scene items. /// **Note:** Will immediately remove all associated scene items.
#[doc(alias = "RemoveInput")] #[doc(alias = "RemoveInput")]
pub async fn remove(&self, name: &str) -> Result<()> { pub async fn remove(&self, input: InputId<'_>) -> Result<()> {
self.client.send_message(Request::Remove { name }).await self.client.send_message(Request::Remove { input }).await
} }
/// Gets the audio balance of an input. /// Gets the audio balance of an input.
#[doc(alias = "GetInputAudioBalance")] #[doc(alias = "GetInputAudioBalance")]
pub async fn audio_balance(&self, name: &str) -> Result<f32> { pub async fn audio_balance(&self, input: InputId<'_>) -> Result<f32> {
self.client self.client
.send_message::<_, responses::AudioBalance>(Request::AudioBalance { name }) .send_message::<_, responses::AudioBalance>(Request::AudioBalance { input })
.await .await
.map(|ab| ab.audio_balance) .map(|ab| ab.audio_balance)
} }
/// Sets the audio balance of an input. /// Sets the audio balance of an input.
#[doc(alias = "SetInputAudioBalance")] #[doc(alias = "SetInputAudioBalance")]
pub async fn set_audio_balance(&self, name: &str, balance: f32) -> Result<()> { pub async fn set_audio_balance(&self, input: InputId<'_>, balance: f32) -> Result<()> {
self.client self.client
.send_message(Request::SetAudioBalance { name, balance }) .send_message(Request::SetAudioBalance { input, balance })
.await .await
} }
@ -183,26 +182,26 @@ impl<'a> Inputs<'a> {
/// ///
/// **Note:** The audio sync offset can be negative too! /// **Note:** The audio sync offset can be negative too!
#[doc(alias = "GetInputAudioSyncOffset")] #[doc(alias = "GetInputAudioSyncOffset")]
pub async fn audio_sync_offset(&self, name: &str) -> Result<Duration> { pub async fn audio_sync_offset(&self, input: InputId<'_>) -> Result<Duration> {
self.client self.client
.send_message::<_, responses::AudioSyncOffset>(Request::AudioSyncOffset { name }) .send_message::<_, responses::AudioSyncOffset>(Request::AudioSyncOffset { input })
.await .await
.map(|aso| aso.input_audio_sync_offset) .map(|aso| aso.input_audio_sync_offset)
} }
/// Sets the audio sync offset of an input. /// Sets the audio sync offset of an input.
#[doc(alias = "SetInputAudioSyncOffset")] #[doc(alias = "SetInputAudioSyncOffset")]
pub async fn set_audio_sync_offset(&self, name: &str, offset: Duration) -> Result<()> { pub async fn set_audio_sync_offset(&self, input: InputId<'_>, offset: Duration) -> Result<()> {
self.client self.client
.send_message(Request::SetAudioSyncOffset { name, offset }) .send_message(Request::SetAudioSyncOffset { input, offset })
.await .await
} }
/// Gets the audio monitor type of input. /// Gets the audio monitor type of input.
#[doc(alias = "GetInputAudioMonitorType")] #[doc(alias = "GetInputAudioMonitorType")]
pub async fn audio_monitor_type(&self, name: &str) -> Result<MonitorType> { pub async fn audio_monitor_type(&self, input: InputId<'_>) -> Result<MonitorType> {
self.client self.client
.send_message::<_, responses::AudioMonitorType>(Request::AudioMonitorType { name }) .send_message::<_, responses::AudioMonitorType>(Request::AudioMonitorType { input })
.await .await
.map(|amt| amt.monitor_type) .map(|amt| amt.monitor_type)
} }
@ -211,28 +210,35 @@ impl<'a> Inputs<'a> {
#[doc(alias = "SetInputAudioMonitorType")] #[doc(alias = "SetInputAudioMonitorType")]
pub async fn set_audio_monitor_type( pub async fn set_audio_monitor_type(
&self, &self,
name: &str, input: InputId<'_>,
monitor_type: MonitorType, monitor_type: MonitorType,
) -> Result<()> { ) -> Result<()> {
self.client self.client
.send_message(Request::SetAudioMonitorType { name, monitor_type }) .send_message(Request::SetAudioMonitorType {
input,
monitor_type,
})
.await .await
} }
/// Gets the enable state of all audio tracks of an input. /// Gets the enable state of all audio tracks of an input.
#[doc(alias = "GetInputAudioTracks")] #[doc(alias = "GetInputAudioTracks")]
pub async fn audio_tracks(&self, name: &str) -> Result<[bool; 6]> { pub async fn audio_tracks(&self, input: InputId<'_>) -> Result<[bool; 6]> {
self.client self.client
.send_message::<_, responses::AudioTracks>(Request::AudioTracks { name }) .send_message::<_, responses::AudioTracks>(Request::AudioTracks { input })
.await .await
.map(|at| at.audio_tracks) .map(|at| at.audio_tracks)
} }
/// Sets the enable state of audio tracks of an input. /// Sets the enable state of audio tracks of an input.
#[doc(alias = "SetInputAudioTracks")] #[doc(alias = "SetInputAudioTracks")]
pub async fn set_audio_tracks(&self, name: &str, tracks: [Option<bool>; 6]) -> Result<()> { pub async fn set_audio_tracks(
&self,
input: InputId<'_>,
tracks: [Option<bool>; 6],
) -> Result<()> {
self.client self.client
.send_message(Request::SetAudioTracks { name, tracks }) .send_message(Request::SetAudioTracks { input, tracks })
.await .await
} }
@ -243,7 +249,7 @@ impl<'a> Inputs<'a> {
#[doc(alias = "GetInputPropertiesListPropertyItems")] #[doc(alias = "GetInputPropertiesListPropertyItems")]
pub async fn properties_list_property_items( pub async fn properties_list_property_items(
&self, &self,
input: &str, input: InputId<'_>,
property: &str, property: &str,
) -> Result<Vec<responses::ListPropertyItem>> { ) -> Result<Vec<responses::ListPropertyItem>> {
self.client self.client
@ -261,7 +267,7 @@ impl<'a> Inputs<'a> {
/// cannot be accessed in any other way. For example, browser sources, where there is a refresh /// cannot be accessed in any other way. For example, browser sources, where there is a refresh
/// button. /// button.
#[doc(alias = "PressInputPropertiesButton")] #[doc(alias = "PressInputPropertiesButton")]
pub async fn press_properties_button(&self, input: &str, property: &str) -> Result<()> { pub async fn press_properties_button(&self, input: InputId<'_>, property: &str) -> Result<()> {
self.client self.client
.send_message(Request::PressPropertiesButton { input, property }) .send_message(Request::PressPropertiesButton { input, property })
.await .await

@ -2,7 +2,9 @@ use time::Duration;
use super::Client; use super::Client;
use crate::{ use crate::{
common::MediaAction, requests::media_inputs::Request, responses::media_inputs as responses, common::MediaAction,
requests::{inputs::InputId, media_inputs::Request},
responses::media_inputs as responses,
Result, Result,
}; };
@ -14,7 +16,7 @@ pub struct MediaInputs<'a> {
impl<'a> MediaInputs<'a> { impl<'a> MediaInputs<'a> {
/// Gets the status of a media input. /// Gets the status of a media input.
#[doc(alias = "GetMediaInputStatus")] #[doc(alias = "GetMediaInputStatus")]
pub async fn status(&self, input: &str) -> Result<responses::MediaStatus> { pub async fn status(&self, input: InputId<'_>) -> Result<responses::MediaStatus> {
self.client.send_message(Request::Status { input }).await self.client.send_message(Request::Status { input }).await
} }
@ -22,7 +24,7 @@ impl<'a> MediaInputs<'a> {
/// ///
/// This request does not perform bounds checking of the cursor position. /// This request does not perform bounds checking of the cursor position.
#[doc(alias = "SetMediaInputCursor")] #[doc(alias = "SetMediaInputCursor")]
pub async fn set_cursor(&self, input: &str, cursor: Duration) -> Result<()> { pub async fn set_cursor(&self, input: InputId<'_>, cursor: Duration) -> Result<()> {
self.client self.client
.send_message(Request::SetCursor { input, cursor }) .send_message(Request::SetCursor { input, cursor })
.await .await
@ -32,7 +34,7 @@ impl<'a> MediaInputs<'a> {
/// ///
/// This request does not perform bounds checking of the cursor position. /// This request does not perform bounds checking of the cursor position.
#[doc(alias = "OffsetMediaInputCursor")] #[doc(alias = "OffsetMediaInputCursor")]
pub async fn offset_cursor(&self, input: &str, offset: Duration) -> Result<()> { pub async fn offset_cursor(&self, input: InputId<'_>, offset: Duration) -> Result<()> {
self.client self.client
.send_message(Request::OffsetCursor { input, offset }) .send_message(Request::OffsetCursor { input, offset })
.await .await
@ -40,7 +42,7 @@ impl<'a> MediaInputs<'a> {
/// Triggers an action on a media input. /// Triggers an action on a media input.
#[doc(alias = "TriggerMediaInputAction")] #[doc(alias = "TriggerMediaInputAction")]
pub async fn trigger_action(&self, input: &str, action: MediaAction) -> Result<()> { pub async fn trigger_action(&self, input: InputId<'_>, action: MediaAction) -> Result<()> {
self.client self.client
.send_message(Request::TriggerAction { input, action }) .send_message(Request::TriggerAction { input, action })
.await .await

@ -8,6 +8,7 @@ use std::{
atomic::{AtomicU64, Ordering}, atomic::{AtomicU64, Ordering},
Arc, Arc,
}, },
time::Duration,
}; };
#[cfg(feature = "events")] #[cfg(feature = "events")]
@ -133,11 +134,15 @@ where
/// not be send to listeners anymore. /// not be send to listeners anymore.
#[cfg_attr(not(feature = "events"), allow(dead_code))] #[cfg_attr(not(feature = "events"), allow(dead_code))]
pub broadcast_capacity: Option<usize>, pub broadcast_capacity: Option<usize>,
/// Maximum wait time to establish a connection with the OBS instance. If this limit is
/// exceeded, the connection ([`Client::connect_with_config`]) call will cancel the attempt and
/// return an [`Error::Timeout`].
pub connect_timeout: Duration,
} }
const OBS_STUDIO_VERSION: Comparator = Comparator { const OBS_STUDIO_VERSION: Comparator = Comparator {
op: Op::GreaterEq, op: Op::GreaterEq,
major: 28, major: 30,
minor: None, minor: None,
patch: None, patch: None,
pre: Prerelease::EMPTY, pre: Prerelease::EMPTY,
@ -169,6 +174,11 @@ where
impl Client { impl Client {
/// Connect to a obs-websocket instance on the given host and port. /// Connect to a obs-websocket instance on the given host and port.
///
/// # Errors
///
/// Will return an [`Error::Timeout`] if the connection couldn't be established within **30
/// seconds**.
pub async fn connect( pub async fn connect(
host: impl AsRef<str>, host: impl AsRef<str>,
port: u16, port: u16,
@ -186,6 +196,7 @@ impl Client {
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
tls: false, tls: false,
broadcast_capacity: None, broadcast_capacity: None,
connect_timeout: Duration::from_secs(30),
}) })
.await .await
} }
@ -196,13 +207,17 @@ impl Client {
H: AsRef<str>, H: AsRef<str>,
P: AsRef<str>, P: AsRef<str>,
{ {
let (socket, _) = tokio_tungstenite::connect_async(format!( let (socket, _) = tokio::time::timeout(
"{}://{}:{}", config.connect_timeout,
if config.tls() { "wss" } else { "ws" }, tokio_tungstenite::connect_async(format!(
config.host.as_ref(), "{}://{}:{}",
config.port if config.tls() { "wss" } else { "ws" },
)) config.host.as_ref(),
config.port
)),
)
.await .await
.map_err(|_| Error::Timeout)?
.map_err(Error::Connect)?; .map_err(Error::Connect)?;
let (mut write, mut read) = socket.split(); let (mut write, mut read) = socket.split();

@ -3,11 +3,14 @@ use serde::{de::DeserializeOwned, Serialize};
use super::Client; use super::Client;
use crate::{ use crate::{
common::BlendMode, common::BlendMode,
requests::scene_items::{ requests::{
CreateSceneItem, Duplicate, Id, Request, SetBlendMode, SetEnabled, SetIndex, SetLocked, scene_items::{
SetPrivateSettings, SetPrivateSettingsInternal, SetTransform, CreateSceneItem, Duplicate, Id, Request, SetBlendMode, SetEnabled, SetIndex, SetLocked,
SetPrivateSettings, SetPrivateSettingsInternal, SetTransform, Source,
},
scenes::SceneId,
}, },
responses::scene_items as responses, responses::{scene_items as responses, sources as source_responses},
Error, Result, Error, Result,
}; };
@ -19,7 +22,7 @@ pub struct SceneItems<'a> {
impl<'a> SceneItems<'a> { impl<'a> SceneItems<'a> {
/// Gets a list of all scene items in a scene. /// Gets a list of all scene items in a scene.
#[doc(alias = "GetSceneItemList")] #[doc(alias = "GetSceneItemList")]
pub async fn list(&self, scene: &str) -> Result<Vec<responses::SceneItem>> { pub async fn list(&self, scene: SceneId<'_>) -> Result<Vec<responses::SceneItem>> {
self.client self.client
.send_message::<_, responses::SceneItemList>(Request::List { scene }) .send_message::<_, responses::SceneItemList>(Request::List { scene })
.await .await
@ -30,7 +33,7 @@ impl<'a> SceneItems<'a> {
/// ///
/// Using groups at all in OBS is discouraged, as they are very broken under the hood. /// Using groups at all in OBS is discouraged, as they are very broken under the hood.
#[doc(alias = "GetGroupSceneItemList")] #[doc(alias = "GetGroupSceneItemList")]
pub async fn list_group(&self, scene: &str) -> Result<Vec<responses::SceneItem>> { pub async fn list_group(&self, scene: SceneId<'_>) -> Result<Vec<responses::SceneItem>> {
self.client self.client
.send_message::<_, responses::SceneItemList>(Request::ListGroup { scene }) .send_message::<_, responses::SceneItemList>(Request::ListGroup { scene })
.await .await
@ -46,6 +49,12 @@ impl<'a> SceneItems<'a> {
.map(|sii| sii.id) .map(|sii| sii.id)
} }
/// Gets the source associated with a scene item.
#[doc(alias = "GetSceneItemSource")]
pub async fn source(&self, get: Source<'_>) -> Result<source_responses::SourceId> {
self.client.send_message(Request::Source(get)).await
}
/// Creates a new scene item using a source. /// Creates a new scene item using a source.
#[doc(alias = "CreateSceneItem")] #[doc(alias = "CreateSceneItem")]
pub async fn create(&self, create: CreateSceneItem<'_>) -> Result<i64> { pub async fn create(&self, create: CreateSceneItem<'_>) -> Result<i64> {
@ -57,7 +66,7 @@ impl<'a> SceneItems<'a> {
/// Removes a scene item from a scene. /// Removes a scene item from a scene.
#[doc(alias = "RemoveSceneItem")] #[doc(alias = "RemoveSceneItem")]
pub async fn remove(&self, scene: &str, item_id: i64) -> Result<()> { pub async fn remove(&self, scene: SceneId<'_>, item_id: i64) -> Result<()> {
self.client self.client
.send_message(Request::Remove { scene, item_id }) .send_message(Request::Remove { scene, item_id })
.await .await
@ -76,7 +85,7 @@ impl<'a> SceneItems<'a> {
#[doc(alias = "GetSceneItemTransform")] #[doc(alias = "GetSceneItemTransform")]
pub async fn transform( pub async fn transform(
&self, &self,
scene: &str, scene: SceneId<'_>,
item_id: i64, item_id: i64,
) -> Result<responses::SceneItemTransform> { ) -> Result<responses::SceneItemTransform> {
self.client self.client
@ -98,7 +107,7 @@ impl<'a> SceneItems<'a> {
/// Gets the enable state of a scene item. /// Gets the enable state of a scene item.
#[doc(alias = "GetSceneItemEnabled")] #[doc(alias = "GetSceneItemEnabled")]
pub async fn enabled(&self, scene: &str, item_id: i64) -> Result<bool> { pub async fn enabled(&self, scene: SceneId<'_>, item_id: i64) -> Result<bool> {
self.client self.client
.send_message::<_, responses::SceneItemEnabled>(Request::Enabled { scene, item_id }) .send_message::<_, responses::SceneItemEnabled>(Request::Enabled { scene, item_id })
.await .await
@ -113,7 +122,7 @@ impl<'a> SceneItems<'a> {
/// Gets the lock state of a scene item. /// Gets the lock state of a scene item.
#[doc(alias = "GetSceneItemLocked")] #[doc(alias = "GetSceneItemLocked")]
pub async fn locked(&self, scene: &str, item_id: i64) -> Result<bool> { pub async fn locked(&self, scene: SceneId<'_>, item_id: i64) -> Result<bool> {
self.client self.client
.send_message::<_, responses::SceneItemLocked>(Request::Locked { scene, item_id }) .send_message::<_, responses::SceneItemLocked>(Request::Locked { scene, item_id })
.await .await
@ -130,7 +139,7 @@ impl<'a> SceneItems<'a> {
/// ///
/// An index of 0 is at the bottom of the source list in the UI. /// An index of 0 is at the bottom of the source list in the UI.
#[doc(alias = "GetSceneItemIndex")] #[doc(alias = "GetSceneItemIndex")]
pub async fn index(&self, scene: &str, item_id: i64) -> Result<u32> { pub async fn index(&self, scene: SceneId<'_>, item_id: i64) -> Result<u32> {
self.client self.client
.send_message::<_, responses::SceneItemIndex>(Request::Index { scene, item_id }) .send_message::<_, responses::SceneItemIndex>(Request::Index { scene, item_id })
.await .await
@ -145,7 +154,7 @@ impl<'a> SceneItems<'a> {
/// Gets the blend mode of a scene item. /// Gets the blend mode of a scene item.
#[doc(alias = "GetSceneItemBlendMode")] #[doc(alias = "GetSceneItemBlendMode")]
pub async fn blend_mode(&self, scene: &str, item_id: i64) -> Result<BlendMode> { pub async fn blend_mode(&self, scene: SceneId<'_>, item_id: i64) -> Result<BlendMode> {
self.client self.client
.send_message::<_, responses::SceneItemBlendMode>(Request::BlendMode { scene, item_id }) .send_message::<_, responses::SceneItemBlendMode>(Request::BlendMode { scene, item_id })
.await .await
@ -160,7 +169,7 @@ impl<'a> SceneItems<'a> {
/// Gets private scene item settings. /// Gets private scene item settings.
#[doc(alias = "GetSceneItemPrivateSettings")] #[doc(alias = "GetSceneItemPrivateSettings")]
pub async fn private_settings<T>(&self, scene: &str, item_id: i64) -> Result<T> pub async fn private_settings<T>(&self, scene: SceneId<'_>, item_id: i64) -> Result<T>
where where
T: DeserializeOwned, T: DeserializeOwned,
{ {

@ -1,6 +1,8 @@
use uuid::Uuid;
use super::Client; use super::Client;
use crate::{ use crate::{
requests::scenes::{Request, SetTransitionOverride}, requests::scenes::{Request, SceneId, SetTransitionOverride},
responses::scenes as responses, responses::scenes as responses,
Result, Result,
}; };
@ -31,18 +33,17 @@ impl<'a> Scenes<'a> {
/// Gets the current program scene. /// Gets the current program scene.
#[doc(alias = "GetCurrentProgramScene")] #[doc(alias = "GetCurrentProgramScene")]
pub async fn current_program_scene(&self) -> Result<String> { pub async fn current_program_scene(&self) -> Result<responses::CurrentProgramScene> {
self.client self.client.send_message(Request::CurrentProgramScene).await
.send_message::<_, responses::CurrentProgramScene>(Request::CurrentProgramScene)
.await
.map(|cps| cps.current_program_scene_name)
} }
/// Sets the current program scene. /// Sets the current program scene.
#[doc(alias = "SetCurrentProgramScene")] #[doc(alias = "SetCurrentProgramScene")]
pub async fn set_current_program_scene(&self, scene: &str) -> Result<()> { pub async fn set_current_program_scene(&self, scene: impl Into<SceneId<'_>>) -> Result<()> {
self.client self.client
.send_message(Request::SetCurrentProgramScene { scene }) .send_message(Request::SetCurrentProgramScene {
scene: scene.into(),
})
.await .await
} }
@ -50,26 +51,25 @@ impl<'a> Scenes<'a> {
/// ///
/// Only available when studio mode is enabled. /// Only available when studio mode is enabled.
#[doc(alias = "GetCurrentPreviewScene")] #[doc(alias = "GetCurrentPreviewScene")]
pub async fn current_preview_scene(&self) -> Result<String> { pub async fn current_preview_scene(&self) -> Result<responses::CurrentPreviewScene> {
self.client self.client.send_message(Request::CurrentPreviewScene).await
.send_message::<_, responses::CurrentPreviewScene>(Request::CurrentPreviewScene)
.await
.map(|cps| cps.current_preview_scene_name)
} }
/// Sets the current preview scene. /// Sets the current preview scene.
/// ///
/// Only available when studio mode is enabled. /// Only available when studio mode is enabled.
#[doc(alias = "SetCurrentPreviewScene")] #[doc(alias = "SetCurrentPreviewScene")]
pub async fn set_current_preview_scene(&self, scene: &str) -> Result<()> { pub async fn set_current_preview_scene(&self, scene: impl Into<SceneId<'_>>) -> Result<()> {
self.client self.client
.send_message(Request::SetCurrentPreviewScene { scene }) .send_message(Request::SetCurrentPreviewScene {
scene: scene.into(),
})
.await .await
} }
/// Sets the name of a scene (rename). /// Sets the name of a scene (rename).
#[doc(alias = "SetSceneName")] #[doc(alias = "SetSceneName")]
pub async fn set_name(&self, scene: &str, new_name: &str) -> Result<()> { pub async fn set_name(&self, scene: SceneId<'_>, new_name: &str) -> Result<()> {
self.client self.client
.send_message(Request::SetName { scene, new_name }) .send_message(Request::SetName { scene, new_name })
.await .await
@ -77,13 +77,16 @@ impl<'a> Scenes<'a> {
/// Creates a new scene in OBS. /// Creates a new scene in OBS.
#[doc(alias = "CreateScene")] #[doc(alias = "CreateScene")]
pub async fn create(&self, name: &str) -> Result<()> { pub async fn create(&self, name: &str) -> Result<Uuid> {
self.client.send_message(Request::Create { name }).await self.client
.send_message::<_, responses::CreateScene>(Request::Create { name })
.await
.map(|cs| cs.uuid)
} }
/// Removes a scene from OBS. /// Removes a scene from OBS.
#[doc(alias = "RemoveScene")] #[doc(alias = "RemoveScene")]
pub async fn remove(&self, scene: &str) -> Result<()> { pub async fn remove(&self, scene: SceneId<'_>) -> Result<()> {
self.client.send_message(Request::Remove { scene }).await self.client.send_message(Request::Remove { scene }).await
} }
@ -91,7 +94,7 @@ impl<'a> Scenes<'a> {
#[doc(alias = "GetSceneSceneTransitionOverride")] #[doc(alias = "GetSceneSceneTransitionOverride")]
pub async fn transition_override( pub async fn transition_override(
&self, &self,
scene: &str, scene: SceneId<'_>,
) -> Result<responses::SceneTransitionOverride> { ) -> Result<responses::SceneTransitionOverride> {
self.client self.client
.send_message(Request::TransitionOverride { scene }) .send_message(Request::TransitionOverride { scene })

@ -1,6 +1,6 @@
use super::Client; use super::Client;
use crate::{ use crate::{
requests::sources::{Request, SaveScreenshot, TakeScreenshot}, requests::sources::{Request, SaveScreenshot, SourceId, TakeScreenshot},
responses::sources as responses, responses::sources as responses,
Result, Result,
}; };
@ -13,8 +13,8 @@ pub struct Sources<'a> {
impl<'a> Sources<'a> { impl<'a> Sources<'a> {
/// Gets the active and show state of a source. /// Gets the active and show state of a source.
#[doc(alias = "GetSourceActive")] #[doc(alias = "GetSourceActive")]
pub async fn active(&self, name: &str) -> Result<responses::SourceActive> { pub async fn active(&self, source: SourceId<'_>) -> Result<responses::SourceActive> {
self.client.send_message(Request::Active { name }).await self.client.send_message(Request::Active { source }).await
} }
/// Gets a Base64-encoded screenshot of a source. /// Gets a Base64-encoded screenshot of a source.

@ -1,8 +1,11 @@
use super::Client; use super::Client;
use crate::{ use crate::{
requests::ui::{ requests::{
OpenSourceProjector, OpenSourceProjectorInternal, OpenVideoMixProjector, inputs::InputId,
OpenVideoMixProjectorInternal, Request, ui::{
OpenSourceProjector, OpenSourceProjectorInternal, OpenVideoMixProjector,
OpenVideoMixProjectorInternal, Request,
},
}, },
responses::ui as responses, responses::ui as responses,
Result, Result,
@ -35,7 +38,7 @@ impl<'a> Ui<'a> {
/// Opens the properties dialog of an input. /// Opens the properties dialog of an input.
#[doc(alias = "OpenInputPropertiesDialog")] #[doc(alias = "OpenInputPropertiesDialog")]
pub async fn open_properties_dialog(&self, input: &str) -> Result<()> { pub async fn open_properties_dialog(&self, input: InputId<'_>) -> Result<()> {
self.client self.client
.send_message(Request::OpenInputPropertiesDialog { input }) .send_message(Request::OpenInputPropertiesDialog { input })
.await .await
@ -43,7 +46,7 @@ impl<'a> Ui<'a> {
/// Opens the filters dialog of an input. /// Opens the filters dialog of an input.
#[doc(alias = "OpenInputFiltersDialog")] #[doc(alias = "OpenInputFiltersDialog")]
pub async fn open_filters_dialog(&self, input: &str) -> Result<()> { pub async fn open_filters_dialog(&self, input: InputId<'_>) -> Result<()> {
self.client self.client
.send_message(Request::OpenInputFiltersDialog { input }) .send_message(Request::OpenInputFiltersDialog { input })
.await .await
@ -51,7 +54,7 @@ impl<'a> Ui<'a> {
/// Opens the interact dialog of an input. /// Opens the interact dialog of an input.
#[doc(alias = "OpenInputInteractDialog")] #[doc(alias = "OpenInputInteractDialog")]
pub async fn open_interact_dialog(&self, input: &str) -> Result<()> { pub async fn open_interact_dialog(&self, input: InputId<'_>) -> Result<()> {
self.client self.client
.send_message(Request::OpenInputInteractDialog { input }) .send_message(Request::OpenInputInteractDialog { input })
.await .await

@ -4,10 +4,17 @@ use std::{collections::BTreeMap, path::PathBuf};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::Duration; use time::Duration;
use uuid::Uuid;
use crate::{ use crate::{
common::{MediaAction, MonitorType}, common::{MediaAction, MonitorType},
responses::{filters::SourceFilter, scene_items::SceneItemTransform}, responses::{
filters::SourceFilter,
ids::{SceneId, TransitionId},
inputs::InputId,
scene_items::SceneItemTransform,
sources::SourceId,
},
}; };
/// All possible event types that can occur while the user interacts with OBS. /// All possible event types that can occur while the user interacts with OBS.
@ -128,6 +135,18 @@ pub enum Event {
#[serde(rename = "filterName")] #[serde(rename = "filterName")]
new_name: String, new_name: String,
}, },
/// A source filter's settings have changed (been updated).
SourceFilterSettingsChanged {
/// Name of the source the filter is on.
#[serde(rename = "sourceName")]
source: String,
/// Name of the filter.
#[serde(rename = "filterName")]
filter: String,
/// New settings object of the filter.
#[serde(rename = "filterSettings")]
settings: serde_json::Value,
},
// -------------------------------- // --------------------------------
// General // General
// -------------------------------- // --------------------------------
@ -159,9 +178,9 @@ pub enum Event {
// -------------------------------- // --------------------------------
/// An input has been created. /// An input has been created.
InputCreated { InputCreated {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// The kind of the input. /// The kind of the input.
#[serde(rename = "inputKind")] #[serde(rename = "inputKind")]
kind: String, kind: String,
@ -177,12 +196,15 @@ pub enum Event {
}, },
/// An input has been removed. /// An input has been removed.
InputRemoved { InputRemoved {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
}, },
/// The name of an input has changed. /// The name of an input has changed.
InputNameChanged { InputNameChanged {
/// UUID of the input.
#[serde(rename = "inputUuid")]
uuid: Uuid,
/// Old name of the input. /// Old name of the input.
#[serde(rename = "oldInputName")] #[serde(rename = "oldInputName")]
old_name: String, old_name: String,
@ -190,13 +212,26 @@ pub enum Event {
#[serde(rename = "inputName")] #[serde(rename = "inputName")]
new_name: String, new_name: String,
}, },
/// An input's settings have changed (been updated).
///
/// Note: On some inputs, changing values in the properties dialog will cause an immediate
/// update. Pressing the _Cancel_ button will revert the settings, resulting in another event
/// being fired.
InputSettingsChanged {
/// Identifier of the input.
#[serde(flatten)]
id: InputId,
/// New settings object of the input.
#[serde(rename = "inputSettings")]
settings: serde_json::Value,
},
/// An input's active state has changed. /// An input's active state has changed.
/// ///
/// When an input is active, it means it's being shown by the program feed. /// When an input is active, it means it's being shown by the program feed.
InputActiveStateChanged { InputActiveStateChanged {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// Whether the input is active. /// Whether the input is active.
#[serde(rename = "videoActive")] #[serde(rename = "videoActive")]
active: bool, active: bool,
@ -205,27 +240,27 @@ pub enum Event {
/// ///
/// When an input is showing, it means it's being shown by the preview or a dialog. /// When an input is showing, it means it's being shown by the preview or a dialog.
InputShowStateChanged { InputShowStateChanged {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// Whether the input is showing. /// Whether the input is showing.
#[serde(rename = "videoShowing")] #[serde(rename = "videoShowing")]
showing: bool, showing: bool,
}, },
/// An input's mute state has changed. /// An input's mute state has changed.
InputMuteStateChanged { InputMuteStateChanged {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// Whether the input is muted. /// Whether the input is muted.
#[serde(rename = "inputMuted")] #[serde(rename = "inputMuted")]
muted: bool, muted: bool,
}, },
/// An input's volume level has changed. /// An input's volume level has changed.
InputVolumeChanged { InputVolumeChanged {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// New volume level multiplier. /// New volume level multiplier.
#[serde(rename = "inputVolumeMul")] #[serde(rename = "inputVolumeMul")]
mul: f64, mul: f64,
@ -235,18 +270,18 @@ pub enum Event {
}, },
/// The audio balance value of an input has changed. /// The audio balance value of an input has changed.
InputAudioBalanceChanged { InputAudioBalanceChanged {
/// Name of the affected input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// New audio balance value of the input. /// New audio balance value of the input.
#[serde(rename = "inputAudioBalance")] #[serde(rename = "inputAudioBalance")]
audio_balance: f64, audio_balance: f64,
}, },
/// The sync offset of an input has changed. /// The sync offset of an input has changed.
InputAudioSyncOffsetChanged { InputAudioSyncOffsetChanged {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// New sync offset in milliseconds. /// New sync offset in milliseconds.
#[serde( #[serde(
rename = "inputAudioSyncOffset", rename = "inputAudioSyncOffset",
@ -256,18 +291,18 @@ pub enum Event {
}, },
/// The audio tracks of an input have changed. /// The audio tracks of an input have changed.
InputAudioTracksChanged { InputAudioTracksChanged {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// Object of audio tracks along with their associated enable states. /// Object of audio tracks along with their associated enable states.
#[serde(rename = "inputAudioTracks")] #[serde(rename = "inputAudioTracks")]
tracks: BTreeMap<String, bool>, tracks: BTreeMap<String, bool>,
}, },
/// The monitor type of an input has changed. /// The monitor type of an input has changed.
InputAudioMonitorTypeChanged { InputAudioMonitorTypeChanged {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// New monitor type of the input. /// New monitor type of the input.
#[serde(rename = "monitorType")] #[serde(rename = "monitorType")]
monitor_type: MonitorType, monitor_type: MonitorType,
@ -283,21 +318,21 @@ pub enum Event {
// -------------------------------- // --------------------------------
/// A media input has started playing. /// A media input has started playing.
MediaInputPlaybackStarted { MediaInputPlaybackStarted {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
}, },
/// A media input has finished playing. /// A media input has finished playing.
MediaInputPlaybackEnded { MediaInputPlaybackEnded {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
}, },
/// An action has been performed on an input. /// An action has been performed on an input.
MediaInputActionTriggered { MediaInputActionTriggered {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: String, id: InputId,
/// Action performed on the input. /// Action performed on the input.
#[serde(rename = "mediaAction")] #[serde(rename = "mediaAction")]
media_action: MediaAction, media_action: MediaAction,
@ -355,12 +390,12 @@ pub enum Event {
// -------------------------------- // --------------------------------
/// A scene item has been created. /// A scene item has been created.
SceneItemCreated { SceneItemCreated {
/// Name of the scene the item was added to. /// Identifier of the scene the item was added to.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: String, scene: SceneId,
/// Name of the underlying source (input/scene). /// Identifier of the underlying source (input/scene).
#[serde(rename = "sourceName")] #[serde(flatten)]
source: String, source: SourceId,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: u64, item_id: u64,
@ -372,30 +407,30 @@ pub enum Event {
/// ///
/// This event is not emitted when the scene the item is in is removed. /// This event is not emitted when the scene the item is in is removed.
SceneItemRemoved { SceneItemRemoved {
/// Name of the scene the item was removed from. /// Identifier of the scene the item was removed from.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: String, scene: SceneId,
/// Name of the underlying source (input/scene). /// Identifier of the underlying source (input/scene).
#[serde(rename = "sourceName")] #[serde(flatten)]
source: String, source: SourceId,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: u64, item_id: u64,
}, },
/// A scene's item list has been re-indexed. /// A scene's item list has been re-indexed.
SceneItemListReindexed { SceneItemListReindexed {
/// Name of the scene. /// Identifier of the scene.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: String, scene: SceneId,
/// Array of scene item objects. /// Array of scene item objects.
#[serde(rename = "sceneItems")] #[serde(rename = "sceneItems")]
items: Vec<BasicSceneItem>, items: Vec<BasicSceneItem>,
}, },
/// A scene item's enable state has changed. /// A scene item's enable state has changed.
SceneItemEnableStateChanged { SceneItemEnableStateChanged {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: String, scene: SceneId,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: u64, item_id: u64,
@ -405,9 +440,9 @@ pub enum Event {
}, },
/// A scene item's lock state has changed. /// A scene item's lock state has changed.
SceneItemLockStateChanged { SceneItemLockStateChanged {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: String, scene: SceneId,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: u64, item_id: u64,
@ -417,18 +452,18 @@ pub enum Event {
}, },
/// A scene item has been selected in the UI. /// A scene item has been selected in the UI.
SceneItemSelected { SceneItemSelected {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: String, scene: SceneId,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: u64, item_id: u64,
}, },
/// The transform/crop of a scene item has changed. /// The transform/crop of a scene item has changed.
SceneItemTransformChanged { SceneItemTransformChanged {
/// The name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: String, scene: SceneId,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: u64, item_id: u64,
@ -441,24 +476,27 @@ pub enum Event {
// -------------------------------- // --------------------------------
/// A new scene has been created. /// A new scene has been created.
SceneCreated { SceneCreated {
/// Name of the new scene. /// Identifier of the new scene.
#[serde(rename = "sceneName")] #[serde(flatten)]
name: String, id: SceneId,
/// Whether the new scene is a group. /// Whether the new scene is a group.
#[serde(rename = "isGroup")] #[serde(rename = "isGroup")]
is_group: bool, is_group: bool,
}, },
/// A scene has been removed. /// A scene has been removed.
SceneRemoved { SceneRemoved {
/// Name of the removed scene. /// Identifier of the removed scene.
#[serde(rename = "sceneName")] #[serde(flatten)]
name: String, id: SceneId,
/// Whether the scene was a group. /// Whether the scene was a group.
#[serde(rename = "isGroup")] #[serde(rename = "isGroup")]
is_group: bool, is_group: bool,
}, },
/// The name of a scene has changed. /// The name of a scene has changed.
SceneNameChanged { SceneNameChanged {
/// UUID of the scene.
#[serde(rename = "sceneUuid")]
uuid: Uuid,
/// Old name of the scene. /// Old name of the scene.
#[serde(rename = "oldSceneName")] #[serde(rename = "oldSceneName")]
old_name: String, old_name: String,
@ -468,15 +506,15 @@ pub enum Event {
}, },
/// The current program scene has changed. /// The current program scene has changed.
CurrentProgramSceneChanged { CurrentProgramSceneChanged {
/// Name of the scene that was switched to. /// Identifier of the scene that was switched to.
#[serde(rename = "sceneName")] #[serde(flatten)]
name: String, id: SceneId,
}, },
/// The current preview scene has changed. /// The current preview scene has changed.
CurrentPreviewSceneChanged { CurrentPreviewSceneChanged {
/// Name of the scene that was switched to. /// Identifier of the scene that was switched to.
#[serde(rename = "sceneName")] #[serde(flatten)]
name: String, id: SceneId,
}, },
/// The list of scenes has changed. /// The list of scenes has changed.
SceneListChanged { SceneListChanged {
@ -488,9 +526,9 @@ pub enum Event {
// -------------------------------- // --------------------------------
/// The current scene transition has changed. /// The current scene transition has changed.
CurrentSceneTransitionChanged { CurrentSceneTransitionChanged {
/// Name of the new transition. /// Identifier of the new transition.
#[serde(rename = "transitionName")] #[serde(flatten)]
name: String, id: TransitionId,
}, },
/// The current scene transition duration has changed. /// The current scene transition duration has changed.
CurrentSceneTransitionDurationChanged { CurrentSceneTransitionDurationChanged {
@ -500,17 +538,17 @@ pub enum Event {
}, },
/// A scene transition has started. /// A scene transition has started.
SceneTransitionStarted { SceneTransitionStarted {
/// Scene transition name. /// Scene transition identifier.
#[serde(rename = "transitionName")] #[serde(flatten)]
name: String, id: TransitionId,
}, },
/// A scene transition has completed fully. /// A scene transition has completed fully.
/// ///
/// **Note:** Does not appear to trigger when the transition is interrupted by the user. /// **Note:** Does not appear to trigger when the transition is interrupted by the user.
SceneTransitionEnded { SceneTransitionEnded {
/// Scene transition name. /// Scene transition identifier.
#[serde(rename = "transitionName")] #[serde(flatten)]
name: String, id: TransitionId,
}, },
/// A scene transition's video has completed fully. /// A scene transition's video has completed fully.
/// ///
@ -520,9 +558,9 @@ pub enum Event {
/// ///
/// **Note:** Appears to be called by every transition, regardless of relevance. /// **Note:** Appears to be called by every transition, regardless of relevance.
SceneTransitionVideoEnded { SceneTransitionVideoEnded {
/// Scene transition name. /// Scene transition identifier.
#[serde(rename = "transitionName")] #[serde(flatten)]
name: String, id: TransitionId,
}, },
// -------------------------------- // --------------------------------
// UI // UI

@ -60,6 +60,9 @@ pub enum Error {
/// An error occurred while trying to connect to the web-socket. /// An error occurred while trying to connect to the web-socket.
#[error("failed to connect to the obs-websocket plugin")] #[error("failed to connect to the obs-websocket plugin")]
Connect(#[source] tokio_tungstenite::tungstenite::Error), Connect(#[source] tokio_tungstenite::tungstenite::Error),
/// The set connection timeout was reached before the connection could be created.
#[error("timeout happened before the connection could be established")]
Timeout,
/// The initial handshake with `obs-websocket` didn't succeed. /// The initial handshake with `obs-websocket` didn't succeed.
#[error("failed to execute the handshake with obs-websocket")] #[error("failed to execute the handshake with obs-websocket")]
Handshake(#[from] crate::client::HandshakeError), Handshake(#[from] crate::client::HandshakeError),
@ -89,9 +92,6 @@ pub enum Error {
/// Optional message to provide additional details about the error. /// Optional message to provide additional details about the error.
message: Option<String>, message: Option<String>,
}, },
/// The obs-websocket API requires authentication but no password was given.
#[error("authentication required but no password provided")]
NoPassword,
/// Unknown flags were found while trying to parse bitflags. /// Unknown flags were found while trying to parse bitflags.
#[error("value {0} contains unknown flags")] #[error("value {0} contains unknown flags")]
UnknownFlags(u8), UnknownFlags(u8),

@ -3,14 +3,18 @@
use serde::Serialize; use serde::Serialize;
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
use super::sources::SourceId;
#[derive(Serialize)] #[derive(Serialize)]
#[serde(tag = "requestType", content = "requestData")] #[serde(tag = "requestType", content = "requestData")]
pub(crate) enum Request<'a> { pub(crate) enum Request<'a> {
#[serde(rename = "GetSourceFilterKindList")]
KindList,
#[serde(rename = "GetSourceFilterList")] #[serde(rename = "GetSourceFilterList")]
List { List {
/// Name of the source. /// Identifier of the source.
#[serde(rename = "sourceName")] #[serde(flatten)]
source: &'a str, source: SourceId<'a>,
}, },
#[serde(rename = "GetSourceFilterDefaultSettings")] #[serde(rename = "GetSourceFilterDefaultSettings")]
DefaultSettings { DefaultSettings {
@ -22,9 +26,9 @@ pub(crate) enum Request<'a> {
Create(CreateInternal<'a>), Create(CreateInternal<'a>),
#[serde(rename = "RemoveSourceFilter")] #[serde(rename = "RemoveSourceFilter")]
Remove { Remove {
/// Name of the source the filter is on. /// Identifier of the source the filter is on.
#[serde(rename = "sourceName")] #[serde(flatten)]
source: &'a str, source: SourceId<'a>,
/// Name of the filter to remove. /// Name of the filter to remove.
#[serde(rename = "filterName")] #[serde(rename = "filterName")]
filter: &'a str, filter: &'a str,
@ -33,9 +37,9 @@ pub(crate) enum Request<'a> {
SetName(SetName<'a>), SetName(SetName<'a>),
#[serde(rename = "GetSourceFilter")] #[serde(rename = "GetSourceFilter")]
Get { Get {
/// Name of the source. /// Identifier of the source.
#[serde(rename = "sourceName")] #[serde(flatten)]
source: &'a str, source: SourceId<'a>,
/// Name of the filter. /// Name of the filter.
#[serde(rename = "filterName")] #[serde(rename = "filterName")]
filter: &'a str, filter: &'a str,
@ -56,8 +60,8 @@ impl<'a> From<Request<'a>> for super::RequestType<'a> {
/// Request information for [`crate::client::Filters::create`]. /// Request information for [`crate::client::Filters::create`].
pub struct Create<'a, T> { pub struct Create<'a, T> {
/// Name of the source to add the filter to. /// Identifier of the source to add the filter to.
pub source: &'a str, pub source: SourceId<'a>,
/// Name of the new filter to be created. /// Name of the new filter to be created.
pub filter: &'a str, pub filter: &'a str,
/// The kind of filter to be created. /// The kind of filter to be created.
@ -70,9 +74,9 @@ pub struct Create<'a, T> {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub(crate) struct CreateInternal<'a> { pub(crate) struct CreateInternal<'a> {
/// Name of the source to add the filter to. /// Identifier of the source to add the filter to.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Name of the new filter to be created. /// Name of the new filter to be created.
#[serde(rename = "filterName")] #[serde(rename = "filterName")]
pub filter: &'a str, pub filter: &'a str,
@ -87,9 +91,9 @@ pub(crate) struct CreateInternal<'a> {
/// Request information for [`crate::client::Filters::set_name`]. /// Request information for [`crate::client::Filters::set_name`].
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct SetName<'a> { pub struct SetName<'a> {
/// Name of the source the filter is on. /// Identifier of the source the filter is on.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Current name of the filter. /// Current name of the filter.
#[serde(rename = "filterName")] #[serde(rename = "filterName")]
pub filter: &'a str, pub filter: &'a str,
@ -101,9 +105,9 @@ pub struct SetName<'a> {
/// Request information for [`crate::client::Filters::set_index`]. /// Request information for [`crate::client::Filters::set_index`].
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct SetIndex<'a> { pub struct SetIndex<'a> {
/// Name of the source the filter is on. /// Identifier of the source the filter is on.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Name of the filter. /// Name of the filter.
#[serde(rename = "filterName")] #[serde(rename = "filterName")]
pub filter: &'a str, pub filter: &'a str,
@ -114,8 +118,8 @@ pub struct SetIndex<'a> {
/// Request information for [`crate::client::Filters::set_settings`]. /// Request information for [`crate::client::Filters::set_settings`].
pub struct SetSettings<'a, T> { pub struct SetSettings<'a, T> {
/// Name of the source the filter is on. /// Identifier of the source the filter is on.
pub source: &'a str, pub source: SourceId<'a>,
/// Name of the filter to set the settings of. /// Name of the filter to set the settings of.
pub filter: &'a str, pub filter: &'a str,
/// Object of settings to apply. /// Object of settings to apply.
@ -127,9 +131,9 @@ pub struct SetSettings<'a, T> {
/// Request information for [`crate::client::Filters::set_settings`]. /// Request information for [`crate::client::Filters::set_settings`].
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub(crate) struct SetSettingsInternal<'a> { pub(crate) struct SetSettingsInternal<'a> {
/// Name of the source the filter is on. /// Identifier of the source the filter is on.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Name of the filter to set the settings of. /// Name of the filter to set the settings of.
#[serde(rename = "filterName")] #[serde(rename = "filterName")]
pub filter: &'a str, pub filter: &'a str,
@ -144,9 +148,9 @@ pub(crate) struct SetSettingsInternal<'a> {
/// Request information for [`crate::client::Filters::set_enabled`]. /// Request information for [`crate::client::Filters::set_enabled`].
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct SetEnabled<'a> { pub struct SetEnabled<'a> {
/// Name of the source the filter is on. /// Identifier of the source the filter is on.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Name of the filter. /// Name of the filter.
#[serde(rename = "filterName")] #[serde(rename = "filterName")]
pub filter: &'a str, pub filter: &'a str,

@ -12,6 +12,9 @@ pub(crate) enum Request<'a> {
/// Name of the hotkey to trigger. /// Name of the hotkey to trigger.
#[serde(rename = "hotkeyName")] #[serde(rename = "hotkeyName")]
name: &'a str, name: &'a str,
/// Name of context of the hotkey to trigger.
#[serde(rename = "contextName")]
context: Option<&'a str>,
}, },
#[serde(rename = "TriggerHotkeyByKeySequence")] #[serde(rename = "TriggerHotkeyByKeySequence")]
TriggerBySequence { TriggerBySequence {

@ -0,0 +1,180 @@
use std::fmt::{self, Display};
use serde::{ser::SerializeStruct, Serialize};
use uuid::Uuid;
macro_rules! item_id {
($ident:ident, $name:literal, $name_field:literal, $uuid_field:literal) => {
#[doc = concat!("Identifier of the", $name, ".")]
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub enum $ident<'a> {
#[doc = concat!("Name of the ", $name, ".")]
Name(&'a str),
#[doc = concat!("UUID of the ", $name, ".")]
Uuid(Uuid),
}
impl $ident<'_> {
/// If the identifier is a name, returns the associated value.
///
/// Will return [`None`] if this identifier is not a name.
pub fn as_name(&self) -> Option<&str> {
match *self {
Self::Name(name) => Some(name),
Self::Uuid(_) => None,
}
}
/// If the identifier is a UUID, returns the associated value.
///
/// Will return [`None`] if this identifier is not a UUID.
pub fn as_uuid(&self) -> Option<Uuid> {
match *self {
Self::Name(_) => None,
Self::Uuid(uuid) => Some(uuid),
}
}
}
impl Default for $ident<'_> {
fn default() -> Self {
Self::Name("")
}
}
impl Display for $ident<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Name(name) => name.fmt(f),
Self::Uuid(uuid) => uuid.fmt(f),
}
}
}
impl PartialEq<str> for $ident<'_> {
fn eq(&self, other: &str) -> bool {
match *self {
Self::Name(name) => name == other,
Self::Uuid(_) => false,
}
}
}
impl PartialEq<Uuid> for $ident<'_> {
fn eq(&self, other: &Uuid) -> bool {
match *self {
Self::Name(_) => false,
Self::Uuid(uuid) => uuid == *other,
}
}
}
impl PartialEq<$ident<'_>> for String {
fn eq(&self, other: &$ident<'_>) -> bool {
other == self.as_str()
}
}
impl PartialEq<$ident<'_>> for &str {
fn eq(&self, other: &$ident<'_>) -> bool {
other == *self
}
}
impl PartialEq<$ident<'_>> for Uuid {
fn eq(&self, other: &$ident<'_>) -> bool {
other == self
}
}
impl<'a> From<&'a str> for $ident<'a> {
fn from(value: &'a str) -> Self {
Self::Name(value)
}
}
impl From<Uuid> for $ident<'_> {
fn from(value: Uuid) -> Self {
Self::Uuid(value)
}
}
impl Serialize for $ident<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut state = serializer.serialize_struct(stringify!($ident), 1)?;
match *self {
Self::Name(name) => {
state.serialize_field($name_field, name)?;
}
Self::Uuid(uuid) => {
state.serialize_field($uuid_field, &uuid)?;
}
}
state.end()
}
}
};
}
item_id!(InputId, "input", "inputName", "inputUuid");
item_id!(SceneId, "scene", "sceneName", "sceneUuid");
item_id!(SourceId, "source", "sourceName", "sourceUuid");
item_id!(
TransitionId,
"transition",
"transitionName",
"transitionUuid"
);
item_id!(
DestinationSceneId,
"destination scene",
"destinationSceneName",
"destinationSceneUuid"
);
macro_rules! convert {
($source:ident, $target:ident) => {
impl<'a> From<$source<'a>> for $target<'a> {
fn from(value: $source<'a>) -> Self {
match value {
$source::Name(name) => Self::Name(name),
$source::Uuid(uuid) => Self::Uuid(uuid),
}
}
}
impl<'a> From<$target<'a>> for $source<'a> {
fn from(value: $target<'a>) -> Self {
match value {
$target::Name(name) => Self::Name(name),
$target::Uuid(uuid) => Self::Uuid(uuid),
}
}
}
};
}
convert!(SceneId, DestinationSceneId);
impl<'a> InputId<'a> {
/// Convert the input identifier into a source identifier.
///
/// This is a one-way operation, as there is no way of telling whether a source ID is an actual
/// input.
pub fn as_source(self) -> SourceId<'a> {
match self {
Self::Name(name) => SourceId::Name(name),
Self::Uuid(uuid) => SourceId::Uuid(uuid),
}
}
}
impl<'a> From<InputId<'a>> for SourceId<'a> {
fn from(value: InputId<'a>) -> Self {
value.as_source()
}
}

@ -4,6 +4,8 @@ use serde::Serialize;
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
use time::Duration; use time::Duration;
pub use super::ids::InputId;
use super::scenes::SceneId;
use crate::common::MonitorType; use crate::common::MonitorType;
#[derive(Serialize)] #[derive(Serialize)]
@ -31,53 +33,53 @@ pub(crate) enum Request<'a> {
}, },
#[serde(rename = "GetInputSettings")] #[serde(rename = "GetInputSettings")]
Settings { Settings {
/// Name of the input to get the settings of. /// The input to get the settings of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "SetInputSettings")] #[serde(rename = "SetInputSettings")]
SetSettings(SetSettingsInternal<'a>), SetSettings(SetSettingsInternal<'a>),
#[serde(rename = "GetInputMute")] #[serde(rename = "GetInputMute")]
Muted { Muted {
/// Name of input to get the mute state of. /// The input to get the mute state of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "SetInputMute")] #[serde(rename = "SetInputMute")]
SetMuted { SetMuted {
/// Name of the input to set the mute state of. /// The input to set the mute state of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
/// Whether to mute the input. /// Whether to mute the input.
#[serde(rename = "inputMuted")] #[serde(rename = "inputMuted")]
muted: bool, muted: bool,
}, },
#[serde(rename = "ToggleInputMute")] #[serde(rename = "ToggleInputMute")]
ToggleMute { ToggleMute {
/// Name of the input to toggle the mute state of. /// The input to toggle the mute state of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "GetInputVolume")] #[serde(rename = "GetInputVolume")]
Volume { Volume {
/// Name of the input to get the volume of. /// The input to get the volume of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "SetInputVolume")] #[serde(rename = "SetInputVolume")]
SetVolume { SetVolume {
/// Name of the input to set the volume of. /// The input to set the volume of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
/// Volume settings in either mul or dB. /// Volume settings in either mul or dB.
#[serde(rename = "volume", flatten)] #[serde(rename = "volume", flatten)]
volume: Volume, volume: Volume,
}, },
#[serde(rename = "SetInputName")] #[serde(rename = "SetInputName")]
SetName { SetName {
/// Current input name. /// Current input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
/// New name for the input. /// New name for the input.
#[serde(rename = "newInputName")] #[serde(rename = "newInputName")]
new: &'a str, new: &'a str,
@ -86,36 +88,36 @@ pub(crate) enum Request<'a> {
Create(CreateInputInternal<'a>), Create(CreateInputInternal<'a>),
#[serde(rename = "RemoveInput")] #[serde(rename = "RemoveInput")]
Remove { Remove {
/// Name of the input to remove. /// The input to remove.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "GetInputAudioBalance")] #[serde(rename = "GetInputAudioBalance")]
AudioBalance { AudioBalance {
/// Name of the input to get the audio balance of. /// The input to get the audio balance of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "SetInputAudioBalance")] #[serde(rename = "SetInputAudioBalance")]
SetAudioBalance { SetAudioBalance {
/// Name of the input to set the audio balance of. /// The input to set the audio balance of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
/// New audio balance value. Must be in range of `0.0..=1.0`. /// New audio balance value. Must be in range of `0.0..=1.0`.
#[serde(rename = "inputAudioBalance")] #[serde(rename = "inputAudioBalance")]
balance: f32, balance: f32,
}, },
#[serde(rename = "GetInputAudioSyncOffset")] #[serde(rename = "GetInputAudioSyncOffset")]
AudioSyncOffset { AudioSyncOffset {
/// Name of the input to get the audio sync offset of. /// The input to get the audio sync offset of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "SetInputAudioSyncOffset")] #[serde(rename = "SetInputAudioSyncOffset")]
SetAudioSyncOffset { SetAudioSyncOffset {
/// Name of the input to set the audio sync offset of. /// The input to set the audio sync offset of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
/// New audio sync offset in milliseconds. /// New audio sync offset in milliseconds.
#[serde( #[serde(
rename = "inputAudioSyncOffset", rename = "inputAudioSyncOffset",
@ -125,30 +127,30 @@ pub(crate) enum Request<'a> {
}, },
#[serde(rename = "GetInputAudioMonitorType")] #[serde(rename = "GetInputAudioMonitorType")]
AudioMonitorType { AudioMonitorType {
/// Name of the input to get the audio monitor type of. /// The input to get the audio monitor type of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "SetInputAudioMonitorType")] #[serde(rename = "SetInputAudioMonitorType")]
SetAudioMonitorType { SetAudioMonitorType {
/// Name of the input to set the audio monitor type of. /// The input to set the audio monitor type of.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
/// Audio monitor type. /// Audio monitor type.
#[serde(rename = "monitorType")] #[serde(rename = "monitorType")]
monitor_type: MonitorType, monitor_type: MonitorType,
}, },
#[serde(rename = "GetInputAudioTracks")] #[serde(rename = "GetInputAudioTracks")]
AudioTracks { AudioTracks {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "SetInputAudioTracks")] #[serde(rename = "SetInputAudioTracks")]
SetAudioTracks { SetAudioTracks {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
name: &'a str, input: InputId<'a>,
/// Track settings to apply. /// Track settings to apply.
#[serde( #[serde(
rename = "inputAudioTracks", rename = "inputAudioTracks",
@ -158,18 +160,18 @@ pub(crate) enum Request<'a> {
}, },
#[serde(rename = "GetInputPropertiesListPropertyItems")] #[serde(rename = "GetInputPropertiesListPropertyItems")]
PropertiesListPropertyItems { PropertiesListPropertyItems {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
/// Name of the list property to get the items of. /// Name of the list property to get the items of.
#[serde(rename = "propertyName")] #[serde(rename = "propertyName")]
property: &'a str, property: &'a str,
}, },
#[serde(rename = "PressInputPropertiesButton")] #[serde(rename = "PressInputPropertiesButton")]
PressPropertiesButton { PressPropertiesButton {
/// Name of the input. /// Identifier of the input.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
/// Name of the button property to press. /// Name of the button property to press.
#[serde(rename = "propertyName")] #[serde(rename = "propertyName")]
property: &'a str, property: &'a str,
@ -184,8 +186,8 @@ impl<'a> From<Request<'a>> for super::RequestType<'a> {
/// Request information for [`crate::client::Inputs::set_settings`]. /// Request information for [`crate::client::Inputs::set_settings`].
pub struct SetSettings<'a, T> { pub struct SetSettings<'a, T> {
/// Name of the input to set the settings of. /// The input to set the settings of.
pub input: &'a str, pub input: InputId<'a>,
/// Object of settings to apply. /// Object of settings to apply.
pub settings: &'a T, pub settings: &'a T,
/// Apply settings on top of existing ones or reset the input to its defaults, then apply /// Apply settings on top of existing ones or reset the input to its defaults, then apply
@ -197,9 +199,9 @@ pub struct SetSettings<'a, T> {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub(crate) struct SetSettingsInternal<'a> { pub(crate) struct SetSettingsInternal<'a> {
/// Name of the input to set the settings of. /// The input to set the settings of.
#[serde(rename = "inputName")] #[serde(flatten)]
pub input: &'a str, pub input: InputId<'a>,
/// Object of settings to apply. /// Object of settings to apply.
#[serde(rename = "inputSettings")] #[serde(rename = "inputSettings")]
pub settings: serde_json::Value, pub settings: serde_json::Value,
@ -224,7 +226,7 @@ pub enum Volume {
/// Request information for [`crate::client::Inputs::create`]. /// Request information for [`crate::client::Inputs::create`].
pub struct Create<'a, T> { pub struct Create<'a, T> {
/// Name of the scene to add the input to as a scene item. /// Name of the scene to add the input to as a scene item.
pub scene: &'a str, pub scene: SceneId<'a>,
/// Name of the new input to created. /// Name of the new input to created.
pub input: &'a str, pub input: &'a str,
/// The kind of input to be created. /// The kind of input to be created.
@ -239,8 +241,8 @@ pub struct Create<'a, T> {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub(crate) struct CreateInputInternal<'a> { pub(crate) struct CreateInputInternal<'a> {
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
#[serde(rename = "inputName")] #[serde(rename = "inputName")]
pub input: &'a str, pub input: &'a str,
#[serde(rename = "inputKind")] #[serde(rename = "inputKind")]

@ -3,6 +3,7 @@
use serde::Serialize; use serde::Serialize;
use time::Duration; use time::Duration;
use super::inputs::InputId;
use crate::common::MediaAction; use crate::common::MediaAction;
#[derive(Serialize)] #[derive(Serialize)]
@ -10,33 +11,33 @@ use crate::common::MediaAction;
pub(crate) enum Request<'a> { pub(crate) enum Request<'a> {
#[serde(rename = "GetMediaInputStatus")] #[serde(rename = "GetMediaInputStatus")]
Status { Status {
/// Name of the media input. /// Identifier of the media input.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "SetMediaInputCursor")] #[serde(rename = "SetMediaInputCursor")]
SetCursor { SetCursor {
/// Name of the media input. /// Identifier of the media input.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
/// New cursor position to set. /// New cursor position to set.
#[serde(rename = "mediaCursor", with = "crate::serde::duration_millis")] #[serde(rename = "mediaCursor", with = "crate::serde::duration_millis")]
cursor: Duration, cursor: Duration,
}, },
#[serde(rename = "OffsetMediaInputCursor")] #[serde(rename = "OffsetMediaInputCursor")]
OffsetCursor { OffsetCursor {
/// Name of the media input. /// Identifier of the media input.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
/// Value to offset the current cursor position by. /// Value to offset the current cursor position by.
#[serde(rename = "mediaCursorOffset", with = "crate::serde::duration_millis")] #[serde(rename = "mediaCursorOffset", with = "crate::serde::duration_millis")]
offset: Duration, offset: Duration,
}, },
#[serde(rename = "TriggerMediaInputAction")] #[serde(rename = "TriggerMediaInputAction")]
TriggerAction { TriggerAction {
/// Name of the media input. /// Identifier of the media input.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
/// Identifier of the media action. /// Identifier of the media action.
#[serde(rename = "mediaAction")] #[serde(rename = "mediaAction")]
action: MediaAction, action: MediaAction,

@ -10,6 +10,7 @@ pub mod custom;
pub mod filters; pub mod filters;
pub mod general; pub mod general;
pub mod hotkeys; pub mod hotkeys;
pub(crate) mod ids;
pub mod inputs; pub mod inputs;
pub(crate) mod media_inputs; pub(crate) mod media_inputs;
pub(crate) mod outputs; pub(crate) mod outputs;

@ -3,6 +3,7 @@
use serde::Serialize; use serde::Serialize;
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
use super::{ids::DestinationSceneId, scenes::SceneId, sources::SourceId};
use crate::common::{Alignment, BlendMode, BoundsType}; use crate::common::{Alignment, BlendMode, BoundsType};
#[derive(Serialize)] #[derive(Serialize)]
@ -10,25 +11,27 @@ use crate::common::{Alignment, BlendMode, BoundsType};
pub(crate) enum Request<'a> { pub(crate) enum Request<'a> {
#[serde(rename = "GetSceneItemList")] #[serde(rename = "GetSceneItemList")]
List { List {
/// Name of the scene to get the items of. /// Identifier of the scene to get the items of.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
}, },
#[serde(rename = "GetGroupSceneItemList")] #[serde(rename = "GetGroupSceneItemList")]
ListGroup { ListGroup {
/// Name of the group to get the items of. /// Identifier of the group to get the items of.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
}, },
#[serde(rename = "GetSceneItemId")] #[serde(rename = "GetSceneItemId")]
Id(Id<'a>), Id(Id<'a>),
#[serde(rename = "GetSceneItemSource")]
Source(Source<'a>),
#[serde(rename = "CreateSceneItem")] #[serde(rename = "CreateSceneItem")]
Create(CreateSceneItem<'a>), Create(CreateSceneItem<'a>),
#[serde(rename = "RemoveSceneItem")] #[serde(rename = "RemoveSceneItem")]
Remove { Remove {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: i64, item_id: i64,
@ -37,9 +40,9 @@ pub(crate) enum Request<'a> {
Duplicate(Duplicate<'a>), Duplicate(Duplicate<'a>),
#[serde(rename = "GetSceneItemTransform")] #[serde(rename = "GetSceneItemTransform")]
Transform { Transform {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: i64, item_id: i64,
@ -48,9 +51,9 @@ pub(crate) enum Request<'a> {
SetTransform(SetTransform<'a>), SetTransform(SetTransform<'a>),
#[serde(rename = "GetSceneItemEnabled")] #[serde(rename = "GetSceneItemEnabled")]
Enabled { Enabled {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: i64, item_id: i64,
@ -59,9 +62,9 @@ pub(crate) enum Request<'a> {
SetEnabled(SetEnabled<'a>), SetEnabled(SetEnabled<'a>),
#[serde(rename = "GetSceneItemLocked")] #[serde(rename = "GetSceneItemLocked")]
Locked { Locked {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: i64, item_id: i64,
@ -70,9 +73,9 @@ pub(crate) enum Request<'a> {
SetLocked(SetLocked<'a>), SetLocked(SetLocked<'a>),
#[serde(rename = "GetSceneItemIndex")] #[serde(rename = "GetSceneItemIndex")]
Index { Index {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: i64, item_id: i64,
@ -81,9 +84,9 @@ pub(crate) enum Request<'a> {
SetIndex(SetIndex<'a>), SetIndex(SetIndex<'a>),
#[serde(rename = "GetSceneItemBlendMode")] #[serde(rename = "GetSceneItemBlendMode")]
BlendMode { BlendMode {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: i64, item_id: i64,
@ -92,9 +95,9 @@ pub(crate) enum Request<'a> {
SetBlendMode(SetBlendMode<'a>), SetBlendMode(SetBlendMode<'a>),
#[serde(rename = "GetSceneItemPrivateSettings")] #[serde(rename = "GetSceneItemPrivateSettings")]
PrivateSettings { PrivateSettings {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
item_id: i64, item_id: i64,
@ -113,9 +116,9 @@ impl<'a> From<Request<'a>> for super::RequestType<'a> {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct Id<'a> { pub struct Id<'a> {
/// Name of the scene or group to search in. /// Identifier of the scene or group to search in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Name of the source to find. /// Name of the source to find.
#[serde(rename = "sourceName")] #[serde(rename = "sourceName")]
pub source: &'a str, pub source: &'a str,
@ -126,16 +129,28 @@ pub struct Id<'a> {
pub search_offset: Option<i32>, pub search_offset: Option<i32>,
} }
/// Request information for [`crate::client::SceneItems::source`].
#[skip_serializing_none]
#[derive(Default, Serialize)]
pub struct Source<'a> {
/// Identifier of the scene the item is in.
#[serde(flatten)]
pub scene: SceneId<'a>,
/// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")]
pub item_id: i64,
}
/// Request information for [`crate::client::SceneItems::create`]. /// Request information for [`crate::client::SceneItems::create`].
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct CreateSceneItem<'a> { pub struct CreateSceneItem<'a> {
/// Name of the scene to create the new item in. /// Identifier of the scene to create the new item in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Name of the source to add to the scene. /// Identifier of the source to add to the scene.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Enable state to apply to the scene item on creation. /// Enable state to apply to the scene item on creation.
#[serde(rename = "sceneItemEnabled")] #[serde(rename = "sceneItemEnabled")]
pub enabled: Option<bool>, pub enabled: Option<bool>,
@ -145,23 +160,23 @@ pub struct CreateSceneItem<'a> {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct Duplicate<'a> { pub struct Duplicate<'a> {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
pub item_id: i64, pub item_id: i64,
/// Name of the scene to create the duplicated item in. /// Identifier of the scene to create the duplicated item in.
#[serde(rename = "destinationSceneName")] #[serde(flatten)]
pub destination: Option<&'a str>, pub destination: Option<DestinationSceneId<'a>>,
} }
/// Request information for [`crate::client::SceneItems::set_transform`]. /// Request information for [`crate::client::SceneItems::set_transform`].
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct SetTransform<'a> { pub struct SetTransform<'a> {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
pub item_id: i64, pub item_id: i64,
@ -291,9 +306,9 @@ pub struct Crop {
/// Request information for [`crate::client::SceneItems::set_enabled`]. /// Request information for [`crate::client::SceneItems::set_enabled`].
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct SetEnabled<'a> { pub struct SetEnabled<'a> {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
pub item_id: i64, pub item_id: i64,
@ -305,9 +320,9 @@ pub struct SetEnabled<'a> {
/// Request information for [`crate::client::SceneItems::set_locked`]. /// Request information for [`crate::client::SceneItems::set_locked`].
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct SetLocked<'a> { pub struct SetLocked<'a> {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
pub item_id: i64, pub item_id: i64,
@ -319,9 +334,9 @@ pub struct SetLocked<'a> {
/// Request information for [`crate::client::SceneItems::set_index`]. /// Request information for [`crate::client::SceneItems::set_index`].
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct SetIndex<'a> { pub struct SetIndex<'a> {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
pub item_id: i64, pub item_id: i64,
@ -333,9 +348,9 @@ pub struct SetIndex<'a> {
/// Request information for [`crate::client::SceneItems::set_blend_mode`]. /// Request information for [`crate::client::SceneItems::set_blend_mode`].
#[derive(Serialize)] #[derive(Serialize)]
pub struct SetBlendMode<'a> { pub struct SetBlendMode<'a> {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
pub item_id: i64, pub item_id: i64,
@ -346,8 +361,8 @@ pub struct SetBlendMode<'a> {
/// Request information for [`crate::client::SceneItems::set_private_settings`]. /// Request information for [`crate::client::SceneItems::set_private_settings`].
pub struct SetPrivateSettings<'a, T> { pub struct SetPrivateSettings<'a, T> {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
pub scene: &'a str, pub scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
pub item_id: i64, pub item_id: i64,
/// Object of settings to apply. /// Object of settings to apply.
@ -359,9 +374,9 @@ pub struct SetPrivateSettings<'a, T> {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub(crate) struct SetPrivateSettingsInternal<'a> { pub(crate) struct SetPrivateSettingsInternal<'a> {
/// Name of the scene the item is in. /// Identifier of the scene the item is in.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
pub item_id: i64, pub item_id: i64,

@ -4,6 +4,9 @@ use serde::Serialize;
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
use time::Duration; use time::Duration;
pub use super::ids::SceneId;
#[skip_serializing_none]
#[derive(Serialize)] #[derive(Serialize)]
#[serde(tag = "requestType", content = "requestData")] #[serde(tag = "requestType", content = "requestData")]
pub(crate) enum Request<'a> { pub(crate) enum Request<'a> {
@ -16,22 +19,22 @@ pub(crate) enum Request<'a> {
#[serde(rename = "SetCurrentProgramScene")] #[serde(rename = "SetCurrentProgramScene")]
SetCurrentProgramScene { SetCurrentProgramScene {
/// Scene to set as the current program scene. /// Scene to set as the current program scene.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
}, },
#[serde(rename = "GetCurrentPreviewScene")] #[serde(rename = "GetCurrentPreviewScene")]
CurrentPreviewScene, CurrentPreviewScene,
#[serde(rename = "SetCurrentPreviewScene")] #[serde(rename = "SetCurrentPreviewScene")]
SetCurrentPreviewScene { SetCurrentPreviewScene {
/// Scene to set as the current preview scene. /// Scene to set as the current preview scene.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
}, },
#[serde(rename = "SetSceneName")] #[serde(rename = "SetSceneName")]
SetName { SetName {
/// Name of the scene to be renamed. /// The scene to be renamed.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
/// New name for the scene. /// New name for the scene.
#[serde(rename = "newSceneName")] #[serde(rename = "newSceneName")]
new_name: &'a str, new_name: &'a str,
@ -44,15 +47,15 @@ pub(crate) enum Request<'a> {
}, },
#[serde(rename = "RemoveScene")] #[serde(rename = "RemoveScene")]
Remove { Remove {
/// Name of the scene to remove. /// The scene to remove.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
}, },
#[serde(rename = "GetSceneSceneTransitionOverride")] #[serde(rename = "GetSceneSceneTransitionOverride")]
TransitionOverride { TransitionOverride {
/// Name of the scene. /// Identifier of the scene.
#[serde(rename = "sceneName")] #[serde(flatten)]
scene: &'a str, scene: SceneId<'a>,
}, },
#[serde(rename = "SetSceneSceneTransitionOverride")] #[serde(rename = "SetSceneSceneTransitionOverride")]
SetTransitionOverride(SetTransitionOverride<'a>), SetTransitionOverride(SetTransitionOverride<'a>),
@ -68,9 +71,9 @@ impl<'a> From<Request<'a>> for super::RequestType<'a> {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Default, Serialize)]
pub struct SetTransitionOverride<'a> { pub struct SetTransitionOverride<'a> {
/// Name of the scene. /// The target scene.
#[serde(rename = "sceneName")] #[serde(flatten)]
pub scene: &'a str, pub scene: SceneId<'a>,
/// Name of the scene transition to use as override. /// Name of the scene transition to use as override.
#[serde(rename = "transitionName")] #[serde(rename = "transitionName")]
pub transition: Option<&'a str>, pub transition: Option<&'a str>,

@ -5,14 +5,16 @@ use std::path::Path;
use serde::Serialize; use serde::Serialize;
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
pub use super::ids::SourceId;
#[derive(Serialize)] #[derive(Serialize)]
#[serde(tag = "requestType", content = "requestData")] #[serde(tag = "requestType", content = "requestData")]
pub(crate) enum Request<'a> { pub(crate) enum Request<'a> {
#[serde(rename = "GetSourceActive")] #[serde(rename = "GetSourceActive")]
Active { Active {
/// Name of the source to get the active state of. /// Identifier of the source to get the active state of.
#[serde(rename = "sourceName")] #[serde(flatten)]
name: &'a str, source: SourceId<'a>,
}, },
#[serde(rename = "GetSourceScreenshot")] #[serde(rename = "GetSourceScreenshot")]
TakeScreenshot(TakeScreenshot<'a>), TakeScreenshot(TakeScreenshot<'a>),
@ -28,11 +30,11 @@ impl<'a> From<Request<'a>> for super::RequestType<'a> {
/// Request information for [`crate::client::Sources::take_screenshot`]. /// Request information for [`crate::client::Sources::take_screenshot`].
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Default, Serialize)] #[derive(Serialize)]
pub struct TakeScreenshot<'a> { pub struct TakeScreenshot<'a> {
/// Name of the source to take a screenshot of. /// Identifier of the source to take a screenshot of.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Image compression format to use. Use [`crate::client::General::version`] to get compatible /// Image compression format to use. Use [`crate::client::General::version`] to get compatible
/// image formats. /// image formats.
#[serde(rename = "imageFormat")] #[serde(rename = "imageFormat")]
@ -53,9 +55,9 @@ pub struct TakeScreenshot<'a> {
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Serialize)] #[derive(Serialize)]
pub struct SaveScreenshot<'a> { pub struct SaveScreenshot<'a> {
/// Name of the source to take a screenshot of. /// Identifier of the source to take a screenshot of.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Image compression format to use. Use [`crate::client::General::version`] to get compatible /// Image compression format to use. Use [`crate::client::General::version`] to get compatible
/// image formats. /// image formats.
#[serde(rename = "imageFormat")] #[serde(rename = "imageFormat")]

@ -3,6 +3,8 @@
use bitflags::bitflags; use bitflags::bitflags;
use serde::Serialize; use serde::Serialize;
use super::{inputs::InputId, sources::SourceId};
#[derive(Serialize)] #[derive(Serialize)]
#[serde(tag = "requestType", content = "requestData")] #[serde(tag = "requestType", content = "requestData")]
pub(crate) enum Request<'a> { pub(crate) enum Request<'a> {
@ -16,21 +18,21 @@ pub(crate) enum Request<'a> {
}, },
#[serde(rename = "OpenInputPropertiesDialog")] #[serde(rename = "OpenInputPropertiesDialog")]
OpenInputPropertiesDialog { OpenInputPropertiesDialog {
/// Name of the input to open the dialog of. /// Identifier of the input to open the dialog of.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "OpenInputFiltersDialog")] #[serde(rename = "OpenInputFiltersDialog")]
OpenInputFiltersDialog { OpenInputFiltersDialog {
/// Name of the input to open the dialog of. /// Identifier of the input to open the dialog of.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "OpenInputInteractDialog")] #[serde(rename = "OpenInputInteractDialog")]
OpenInputInteractDialog { OpenInputInteractDialog {
/// Name of the input to open the dialog of. /// Identifier of the input to open the dialog of.
#[serde(rename = "inputName")] #[serde(flatten)]
input: &'a str, input: InputId<'a>,
}, },
#[serde(rename = "GetMonitorList")] #[serde(rename = "GetMonitorList")]
GetMonitorList, GetMonitorList,
@ -67,8 +69,8 @@ pub(crate) struct OpenVideoMixProjectorInternal {
/// Request information for [`crate::client::Ui::open_source_projector`]. /// Request information for [`crate::client::Ui::open_source_projector`].
pub struct OpenSourceProjector<'a> { pub struct OpenSourceProjector<'a> {
/// Name of the source to open a projector for. /// Identifier of the source to open a projector for.
pub source: &'a str, pub source: SourceId<'a>,
/// Optional location for the new projector window. /// Optional location for the new projector window.
pub location: Option<Location>, pub location: Option<Location>,
} }
@ -76,9 +78,9 @@ pub struct OpenSourceProjector<'a> {
/// Request information for [`crate::client::Ui::open_source_projector`]. /// Request information for [`crate::client::Ui::open_source_projector`].
#[derive(Serialize)] #[derive(Serialize)]
pub(crate) struct OpenSourceProjectorInternal<'a> { pub(crate) struct OpenSourceProjectorInternal<'a> {
/// Name of the source to open a projector for. /// Identifier of the source to open a projector for.
#[serde(rename = "sourceName")] #[serde(flatten)]
pub source: &'a str, pub source: SourceId<'a>,
/// Optional location for the new projector window. /// Optional location for the new projector window.
#[serde(flatten)] #[serde(flatten)]
pub location: Option<LocationInternal>, pub location: Option<LocationInternal>,

@ -2,6 +2,14 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Response value for [`crate::client::Filters::get_source_filter_kind_list`].
#[derive(Debug, Deserialize)]
pub(crate) struct FilterKinds {
/// Array of source filter kinds.
#[serde(rename = "sourceFilterKinds")]
pub kinds: Vec<String>,
}
/// Response value for [`crate::client::Filters::get_source_filter_list`]. /// Response value for [`crate::client::Filters::get_source_filter_list`].
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub(crate) struct Filters { pub(crate) struct Filters {

@ -0,0 +1,141 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
macro_rules! item_id {
($ident:ident, $name:literal, $name_field:literal, $uuid_field:literal) => {
#[doc = concat!("Identifier of the", $name, ".")]
#[derive(
Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize,
)]
pub struct $ident {
#[doc = concat!("Name of the", $name, ".")]
#[serde(rename = $name_field)]
pub name: String,
#[doc = concat!("UUID of the", $name, ".")]
#[serde(rename = $uuid_field)]
pub uuid: Uuid,
}
impl PartialEq<$ident> for String {
fn eq(&self, other: &$ident) -> bool {
other == self.as_str()
}
}
impl PartialEq<$ident> for &str {
fn eq(&self, other: &$ident) -> bool {
other == *self
}
}
impl PartialEq<$ident> for Uuid {
fn eq(&self, other: &$ident) -> bool {
other == self
}
}
impl PartialEq<str> for $ident {
fn eq(&self, other: &str) -> bool {
self.name == other
}
}
impl PartialEq<Uuid> for $ident {
fn eq(&self, other: &Uuid) -> bool {
self.uuid == *other
}
}
};
}
item_id!(InputId, "input", "inputName", "inputUuid");
item_id!(SceneId, "scene", "sceneName", "sceneUuid");
item_id!(SourceId, "source", "sourceName", "sourceUuid");
item_id!(
TransitionId,
"transition",
"transitionName",
"transitionUuid"
);
item_id!(
CurrentPreviewSceneId,
"current preview scene",
"currentPreviewSceneName",
"currentPreviewSceneUuid"
);
item_id!(
CurrentProgramSceneId,
"current program scene",
"currentProgramSceneName",
"currentProgramSceneUuid"
);
item_id!(
CurrentSceneTransitionId,
"current scene transition",
"currentSceneTransitionName",
"currentSceneTransitionUuid"
);
macro_rules! convert {
($source:ident, $target:ident) => {
impl From<$source> for $target {
fn from(value: $source) -> Self {
Self {
name: value.name,
uuid: value.uuid,
}
}
}
impl From<$target> for $source {
fn from(value: $target) -> Self {
Self {
name: value.name,
uuid: value.uuid,
}
}
}
};
}
convert!(SceneId, CurrentPreviewSceneId);
convert!(SceneId, CurrentProgramSceneId);
convert!(CurrentPreviewSceneId, CurrentProgramSceneId);
convert!(TransitionId, CurrentSceneTransitionId);
macro_rules! request {
($ident:ident) => {
impl From<$ident> for crate::requests::ids::$ident<'_> {
fn from(value: $ident) -> Self {
Self::Uuid(value.uuid)
}
}
impl From<&$ident> for crate::requests::ids::$ident<'_> {
fn from(value: &$ident) -> Self {
Self::Uuid(value.uuid)
}
}
impl PartialEq<$ident> for crate::requests::ids::$ident<'_> {
fn eq(&self, other: &$ident) -> bool {
match *self {
Self::Name(name) => name == other.name,
Self::Uuid(uuid) => uuid == other.uuid,
}
}
}
impl PartialEq<crate::requests::ids::$ident<'_>> for $ident {
fn eq(&self, other: &crate::requests::ids::$ident<'_>) -> bool {
other == self
}
}
};
}
request!(InputId);
request!(SceneId);
request!(SourceId);
request!(TransitionId);

@ -2,7 +2,9 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::Duration; use time::Duration;
use uuid::Uuid;
pub use super::ids::InputId;
use crate::common::MonitorType; use crate::common::MonitorType;
/// Response value for [`crate::client::Inputs::get_input_list`]. /// Response value for [`crate::client::Inputs::get_input_list`].
@ -16,9 +18,9 @@ pub(crate) struct Inputs {
/// Response value for [`crate::client::Inputs::list`]. /// Response value for [`crate::client::Inputs::list`].
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Input { pub struct Input {
/// Name of the input source. /// Identifier of the input source.
#[serde(rename = "inputName")] #[serde(flatten)]
pub name: String, pub id: InputId,
/// Version input kind. /// Version input kind.
#[serde(rename = "inputKind")] #[serde(rename = "inputKind")]
pub kind: String, pub kind: String,
@ -154,8 +156,12 @@ pub struct ListPropertyItem {
pub value: serde_json::Value, pub value: serde_json::Value,
} }
#[derive(Debug, Deserialize)] /// Response value for [`crate::client::Inputs::create`].
pub(crate) struct SceneItemId { #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SceneItemId {
/// UUID of the newly created input.
#[serde(rename = "inputUuid")]
pub input_uuid: Uuid,
/// Numeric ID of the scene item. /// Numeric ID of the scene item.
#[serde(rename = "sceneItemId")] #[serde(rename = "sceneItemId")]
pub scene_item_id: i64, pub scene_item_id: i64,

@ -4,6 +4,7 @@ pub mod config;
pub mod filters; pub mod filters;
pub mod general; pub mod general;
pub(crate) mod hotkeys; pub(crate) mod hotkeys;
pub(crate) mod ids;
pub mod inputs; pub mod inputs;
pub mod media_inputs; pub mod media_inputs;
pub mod outputs; pub mod outputs;
@ -39,6 +40,7 @@ pub(crate) enum ServerMessage {
/// `obs-websocket` is responding to a request coming from a client. /// `obs-websocket` is responding to a request coming from a client.
RequestResponse(RequestResponse), RequestResponse(RequestResponse),
/// `obs-websocket` is responding to a request batch coming from the client. /// `obs-websocket` is responding to a request batch coming from the client.
#[allow(dead_code)]
RequestBatchResponse(RequestBatchResponse), RequestBatchResponse(RequestBatchResponse),
} }

@ -2,16 +2,19 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::Duration; use time::Duration;
use uuid::Uuid;
pub use super::ids::{CurrentPreviewSceneId, CurrentProgramSceneId, SceneId};
/// Response value for [`crate::client::Scenes::list`]. /// Response value for [`crate::client::Scenes::list`].
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Scenes { pub struct Scenes {
/// Current program scene. /// Current program scene identifier. Can be [`None`] if internal state desync.
#[serde(rename = "currentProgramSceneName")] #[serde(flatten)]
pub current_program_scene_name: Option<String>, pub current_program_scene: Option<CurrentProgramSceneId>,
/// Current preview scene. [`None`] if not in studio mode. /// Current preview scene identifier. [`None`] if not in studio mode.
#[serde(rename = "currentPreviewSceneName")] #[serde(flatten)]
pub current_preview_scene_name: Option<String>, pub current_preview_scene: Option<CurrentPreviewSceneId>,
/// Array of scenes in OBS. /// Array of scenes in OBS.
#[serde(rename = "scenes")] #[serde(rename = "scenes")]
pub scenes: Vec<Scene>, pub scenes: Vec<Scene>,
@ -38,20 +41,27 @@ pub(crate) struct Groups {
/// Response value for /// Response value for
/// [`crate::client::Scenes::get_current_program_scene`]. /// [`crate::client::Scenes::get_current_program_scene`].
#[derive(Debug, Deserialize)] #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub(crate) struct CurrentProgramScene { pub struct CurrentProgramScene {
/// Current program scene. /// Current program scene identifier.
#[serde(rename = "currentProgramSceneName")] #[serde(flatten)]
pub current_program_scene_name: String, pub id: SceneId,
} }
/// Response value for /// Response value for
/// [`crate::client::Scenes::get_current_preview_scene`]. /// [`crate::client::Scenes::get_current_preview_scene`].
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct CurrentPreviewScene {
/// Current preview scene identifier.
#[serde(flatten)]
pub id: SceneId,
}
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub(crate) struct CurrentPreviewScene { pub(crate) struct CreateScene {
/// Current preview scene. /// UUID of the created scene.
#[serde(rename = "currentPreviewSceneName")] #[serde(rename = "sceneUuid")]
pub current_preview_scene_name: String, pub uuid: Uuid,
} }
/// Response value for [`crate::client::Scenes::transition_override`]. /// Response value for [`crate::client::Scenes::transition_override`].

@ -2,6 +2,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub use super::ids::SourceId;
/// Response value for [`crate::client::Sources::active`]. /// Response value for [`crate::client::Sources::active`].
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SourceActive { pub struct SourceActive {

@ -3,6 +3,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use time::Duration; use time::Duration;
pub use super::ids::{CurrentSceneTransitionId, TransitionId};
/// Response value for /// Response value for
/// [`crate::client::Transitions::get_transition_kind_list`]. /// [`crate::client::Transitions::get_transition_kind_list`].
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -15,9 +17,9 @@ pub(crate) struct TransitionKinds {
/// Response value for [`crate::client::Transitions::list`]. /// Response value for [`crate::client::Transitions::list`].
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct SceneTransitionList { pub struct SceneTransitionList {
/// Name of the current scene transition. /// Identifier of the current scene transition.
#[serde(rename = "currentSceneTransitionName")] #[serde(flatten)]
pub current_scene_transition_name: Option<String>, pub current_scene_transition: Option<CurrentSceneTransitionId>,
/// Kind of the current scene transition. /// Kind of the current scene transition.
#[serde(rename = "currentSceneTransitionKind")] #[serde(rename = "currentSceneTransitionKind")]
pub current_scene_transition_kind: Option<String>, pub current_scene_transition_kind: Option<String>,
@ -29,9 +31,9 @@ pub struct SceneTransitionList {
/// Response value for [`crate::client::Transitions::list`] as part of [`SceneTransitionList`]. /// Response value for [`crate::client::Transitions::list`] as part of [`SceneTransitionList`].
#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Transition { pub struct Transition {
/// Name of the transition. /// Identifier of the transition.
#[serde(rename = "transitionName")] #[serde(flatten)]
pub name: String, pub id: TransitionId,
/// Kind of the transition. /// Kind of the transition.
#[serde(rename = "transitionKind")] #[serde(rename = "transitionKind")]
pub kind: String, pub kind: String,
@ -46,9 +48,9 @@ pub struct Transition {
/// Response value for [`crate::client::Transitions::current`]. /// Response value for [`crate::client::Transitions::current`].
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct CurrentSceneTransition { pub struct CurrentSceneTransition {
/// Name of the transition. /// Identifier of the transition.
#[serde(rename = "transitionName")] #[serde(flatten)]
pub name: String, pub id: TransitionId,
/// Kind of the transition. /// Kind of the transition.
#[serde(rename = "transitionKind")] #[serde(rename = "transitionKind")]
pub kind: String, pub kind: String,

@ -2,21 +2,22 @@ use std::{env, sync::Once};
use anyhow::{ensure, Result}; use anyhow::{ensure, Result};
use obws::{ use obws::{
requests::{inputs::InputId, scenes::SceneId},
responses::{filters::SourceFilter, inputs::Input, scenes::Scene}, responses::{filters::SourceFilter, inputs::Input, scenes::Scene},
Client, Client,
}; };
pub const TEST_PROFILE: &str = "OBWS-TEST"; pub const TEST_PROFILE: &str = "OBWS-TEST";
pub const TEST_SCENE: &str = "OBWS-TEST-Scene"; pub const TEST_SCENE: SceneId<'_> = SceneId::Name("OBWS-TEST-Scene");
pub const TEST_SCENE_2: &str = "OBWS-TEST-Scene2"; pub const TEST_SCENE_2: SceneId<'_> = SceneId::Name("OBWS-TEST-Scene2");
pub const TEST_SCENE_RENAME: &str = "OBWS-TEST-Scene-Renamed"; pub const TEST_SCENE_RENAME: SceneId<'_> = SceneId::Name("OBWS-TEST-Scene-Renamed");
pub const TEST_SCENE_CREATE: &str = "OBWS-TEST-Scene-Created"; pub const TEST_SCENE_CREATE: SceneId<'_> = SceneId::Name("OBWS-TEST-Scene-Created");
pub const TEST_TEXT: &str = "OBWS-TEST-Text"; pub const TEST_TEXT: InputId<'_> = InputId::Name("OBWS-TEST-Text");
pub const TEST_TEXT_2: &str = "OBWS-TEST-Text2"; pub const TEST_TEXT_2: InputId<'_> = InputId::Name("OBWS-TEST-Text2");
pub const TEST_BROWSER: &str = "OBWS-TEST-Browser"; pub const TEST_BROWSER: InputId<'_> = InputId::Name("OBWS-TEST-Browser");
pub const TEST_BROWSER_RENAME: &str = "OBWS-TEST-Browser-Renamed"; pub const TEST_BROWSER_RENAME: InputId<'_> = InputId::Name("OBWS-TEST-Browser-Renamed");
pub const TEST_MEDIA: &str = "OBWS-TEST-Media"; pub const TEST_MEDIA: InputId<'_> = InputId::Name("OBWS-TEST-Media");
pub const TEST_GROUP: &str = "OBWS-TEST-Group"; pub const TEST_GROUP: SceneId<'_> = SceneId::Name("OBWS-TEST-Group");
pub const TEST_TRANSITION: &str = "OBWS-TEST-Transition"; pub const TEST_TRANSITION: &str = "OBWS-TEST-Transition";
pub const TEST_FILTER: &str = "OBWS-TEST-Filter"; pub const TEST_FILTER: &str = "OBWS-TEST-Filter";
pub const TEST_FILTER_2: &str = "OBWS-TEST-Filter2"; pub const TEST_FILTER_2: &str = "OBWS-TEST-Filter2";
@ -84,7 +85,7 @@ async fn ensure_obs_setup(client: &Client) -> Result<()> {
ensure!( ensure!(
inputs.iter().any(is_required_text_2_input), inputs.iter().any(is_required_text_2_input),
"text input `{}` not found, required for inputs tests", "text input `{}` not found, required for inputs tests",
TEST_TEXT TEST_TEXT_2
); );
ensure!( ensure!(
inputs.iter().any(is_required_browser_input), inputs.iter().any(is_required_browser_input),
@ -102,7 +103,7 @@ async fn ensure_obs_setup(client: &Client) -> Result<()> {
TEST_BROWSER_RENAME TEST_BROWSER_RENAME
); );
let filters = client.filters().list(TEST_TEXT).await?; let filters = client.filters().list(TEST_TEXT.as_source()).await?;
ensure!( ensure!(
filters.iter().any(is_required_filter), filters.iter().any(is_required_filter),
"filter `{}` not found, required for filters tests", "filter `{}` not found, required for filters tests",
@ -179,23 +180,23 @@ fn is_required_group(group: &str) -> bool {
} }
fn is_required_text_input(input: &Input) -> bool { fn is_required_text_input(input: &Input) -> bool {
input.name == TEST_TEXT && is_text_input(input) input.id == TEST_TEXT && is_text_input(input)
} }
fn is_required_text_2_input(input: &Input) -> bool { fn is_required_text_2_input(input: &Input) -> bool {
input.name == TEST_TEXT_2 && is_text_input(input) input.id == TEST_TEXT_2 && is_text_input(input)
} }
fn is_required_browser_input(input: &Input) -> bool { fn is_required_browser_input(input: &Input) -> bool {
input.name == TEST_BROWSER && is_browser_input(input) input.id == TEST_BROWSER && is_browser_input(input)
} }
fn is_required_media_input(input: &Input) -> bool { fn is_required_media_input(input: &Input) -> bool {
input.name == TEST_MEDIA && is_media_input(input) input.id == TEST_MEDIA && is_media_input(input)
} }
fn is_renamed_input(input: &Input) -> bool { fn is_renamed_input(input: &Input) -> bool {
input.name == TEST_BROWSER_RENAME input.id == TEST_BROWSER_RENAME
} }
fn is_text_input(input: &Input) -> bool { fn is_text_input(input: &Input) -> bool {

@ -10,48 +10,48 @@ async fn filters() -> Result<()> {
let client = common::new_client().await?; let client = common::new_client().await?;
let client = client.filters(); let client = client.filters();
client.list(TEST_TEXT).await?; client.list(TEST_TEXT.as_source()).await?;
client client
.default_settings::<serde_json::Value>(FILTER_COLOR) .default_settings::<serde_json::Value>(FILTER_COLOR)
.await?; .await?;
client client
.create(Create { .create(Create {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
filter: TEST_FILTER_2, filter: TEST_FILTER_2,
kind: FILTER_COLOR, kind: FILTER_COLOR,
settings: Some(serde_json::Map::new()), settings: Some(serde_json::Map::new()),
}) })
.await?; .await?;
client.remove(TEST_TEXT, TEST_FILTER_2).await?; client.remove(TEST_TEXT.as_source(), TEST_FILTER_2).await?;
client client
.set_name(SetName { .set_name(SetName {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
filter: TEST_FILTER, filter: TEST_FILTER,
new_name: TEST_FILTER_RENAME, new_name: TEST_FILTER_RENAME,
}) })
.await?; .await?;
client client
.set_name(SetName { .set_name(SetName {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
filter: TEST_FILTER_RENAME, filter: TEST_FILTER_RENAME,
new_name: TEST_FILTER, new_name: TEST_FILTER,
}) })
.await?; .await?;
client.get(TEST_TEXT, TEST_FILTER).await?; client.get(TEST_TEXT.as_source(), TEST_FILTER).await?;
client client
.set_index(SetIndex { .set_index(SetIndex {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
filter: TEST_FILTER, filter: TEST_FILTER,
index: 0, index: 0,
}) })
.await?; .await?;
client client
.set_settings(SetSettings { .set_settings(SetSettings {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
filter: TEST_FILTER, filter: TEST_FILTER,
settings: serde_json::Map::new(), settings: serde_json::Map::new(),
overlay: Some(true), overlay: Some(true),
@ -59,14 +59,14 @@ async fn filters() -> Result<()> {
.await?; .await?;
client client
.set_enabled(SetEnabled { .set_enabled(SetEnabled {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
filter: TEST_FILTER, filter: TEST_FILTER,
enabled: false, enabled: false,
}) })
.await?; .await?;
client client
.set_enabled(SetEnabled { .set_enabled(SetEnabled {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
filter: TEST_FILTER, filter: TEST_FILTER,
enabled: true, enabled: true,
}) })

@ -9,7 +9,7 @@ async fn hotkeys() -> Result<()> {
let client = client.hotkeys(); let client = client.hotkeys();
client.list().await?; client.list().await?;
client.trigger_by_name("ReplayBuffer.Save").await?; client.trigger_by_name("ReplayBuffer.Save", None).await?;
client client
.trigger_by_sequence("OBS_KEY_P", KeyModifiers::default()) .trigger_by_sequence("OBS_KEY_P", KeyModifiers::default())
.await?; .await?;

@ -42,8 +42,12 @@ async fn inputs() -> Result<()> {
.set_volume(TEST_MEDIA, Volume::Mul(volume.mul)) .set_volume(TEST_MEDIA, Volume::Mul(volume.mul))
.await?; .await?;
client.set_name(TEST_BROWSER, TEST_BROWSER_RENAME).await?; client
client.set_name(TEST_BROWSER_RENAME, TEST_BROWSER).await?; .set_name(TEST_BROWSER, TEST_BROWSER_RENAME.as_name().unwrap())
.await?;
client
.set_name(TEST_BROWSER_RENAME, TEST_BROWSER.as_name().unwrap())
.await?;
let balance = client.audio_balance(TEST_MEDIA).await?; let balance = client.audio_balance(TEST_MEDIA).await?;
client.set_audio_balance(TEST_MEDIA, balance / 2.0).await?; client.set_audio_balance(TEST_MEDIA, balance / 2.0).await?;

@ -3,60 +3,29 @@
use std::time::Duration; use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use obws::events::{Event, OutputState};
use tokio::time; use tokio::time;
use crate::{common, wait_for}; use crate::common;
const OUTPUT_VIRTUALCAM: &str = "virtualcam_output"; const OUTPUT_VIRTUALCAM: &str = "virtualcam_output";
#[tokio::test] #[tokio::test]
async fn outputs() -> Result<()> { async fn outputs() -> Result<()> {
let client = common::new_client().await?; let client = common::new_client().await?;
let events = client.events()?;
let client = client.outputs(); let client = client.outputs();
tokio::pin!(events); time::sleep(Duration::from_secs(1)).await;
client.list().await?; client.list().await?;
client.status(OUTPUT_VIRTUALCAM).await?; client.status(OUTPUT_VIRTUALCAM).await?;
client.toggle(OUTPUT_VIRTUALCAM).await?; client.toggle(OUTPUT_VIRTUALCAM).await?;
wait_for!(
events,
Event::VirtualcamStateChanged {
state: OutputState::Started,
..
}
);
time::sleep(Duration::from_secs(1)).await; time::sleep(Duration::from_secs(1)).await;
client.toggle(OUTPUT_VIRTUALCAM).await?; client.toggle(OUTPUT_VIRTUALCAM).await?;
wait_for!(
events,
Event::VirtualcamStateChanged {
state: OutputState::Stopped,
..
}
);
time::sleep(Duration::from_secs(1)).await; time::sleep(Duration::from_secs(1)).await;
client.start(OUTPUT_VIRTUALCAM).await?; client.start(OUTPUT_VIRTUALCAM).await?;
wait_for!(
events,
Event::VirtualcamStateChanged {
state: OutputState::Started,
..
}
);
time::sleep(Duration::from_secs(1)).await; time::sleep(Duration::from_secs(1)).await;
client.stop(OUTPUT_VIRTUALCAM).await?; client.stop(OUTPUT_VIRTUALCAM).await?;
wait_for!(
events,
Event::VirtualcamStateChanged {
state: OutputState::Stopped,
..
}
);
time::sleep(Duration::from_secs(1)).await;
let settings = client let settings = client
.settings::<serde_json::Value>(OUTPUT_VIRTUALCAM) .settings::<serde_json::Value>(OUTPUT_VIRTUALCAM)

@ -20,7 +20,7 @@ async fn scene_items() -> Result<()> {
let test_text_id = client let test_text_id = client
.id(Id { .id(Id {
scene: TEST_SCENE, scene: TEST_SCENE,
source: TEST_TEXT, source: TEST_TEXT.as_name().unwrap(),
search_offset: None, search_offset: None,
}) })
.await?; .await?;
@ -29,7 +29,7 @@ async fn scene_items() -> Result<()> {
.duplicate(Duplicate { .duplicate(Duplicate {
scene: TEST_SCENE, scene: TEST_SCENE,
item_id: test_text_id, item_id: test_text_id,
destination: Some(TEST_SCENE_2), destination: Some(TEST_SCENE_2.into()),
}) })
.await?; .await?;
client.remove(TEST_SCENE_2, id).await?; client.remove(TEST_SCENE_2, id).await?;
@ -37,7 +37,7 @@ async fn scene_items() -> Result<()> {
let id = client let id = client
.create(CreateSceneItem { .create(CreateSceneItem {
scene: TEST_SCENE_2, scene: TEST_SCENE_2,
source: TEST_TEXT, source: TEST_TEXT.as_source(),
enabled: Some(true), enabled: Some(true),
}) })
.await?; .await?;

@ -16,19 +16,27 @@ async fn scenes() -> Result<()> {
client.list_groups().await?; client.list_groups().await?;
let current = client.current_program_scene().await?; let current = client.current_program_scene().await?;
let other = &scenes.iter().find(|s| s.name != current).unwrap().name; let other = &scenes.iter().find(|s| s.name != current.id).unwrap().name;
client.set_current_program_scene(other).await?; client.set_current_program_scene(other.as_str()).await?;
client.set_current_program_scene(&current).await?; client
.set_current_program_scene(current.id.name.as_str())
.await?;
let current = client.current_preview_scene().await?; let current = client.current_preview_scene().await?;
let other = &scenes.iter().find(|s| s.name != current).unwrap().name; let other = &scenes.iter().find(|s| s.name != current.id).unwrap().name;
client.set_current_preview_scene(other).await?; client.set_current_preview_scene(other.as_str()).await?;
client.set_current_preview_scene(&current).await?; client
.set_current_preview_scene(current.id.name.as_str())
.await?;
client.set_name(TEST_SCENE, TEST_SCENE_RENAME).await?; client
client.set_name(TEST_SCENE_RENAME, TEST_SCENE).await?; .set_name(TEST_SCENE, TEST_SCENE_RENAME.as_name().unwrap())
.await?;
client
.set_name(TEST_SCENE_RENAME, TEST_SCENE.as_name().unwrap())
.await?;
client.create(TEST_SCENE_CREATE).await?; client.create(TEST_SCENE_CREATE.as_name().unwrap()).await?;
client.remove(TEST_SCENE_CREATE).await?; client.remove(TEST_SCENE_CREATE).await?;
let to = client.transition_override(TEST_SCENE).await?; let to = client.transition_override(TEST_SCENE).await?;

@ -10,10 +10,10 @@ async fn sources() -> Result<()> {
let client = common::new_client().await?; let client = common::new_client().await?;
let client = client.sources(); let client = client.sources();
client.active(TEST_TEXT).await?; client.active(TEST_TEXT.as_source()).await?;
client client
.take_screenshot(TakeScreenshot { .take_screenshot(TakeScreenshot {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
width: Some(100), width: Some(100),
height: Some(100), height: Some(100),
compression_quality: Some(50), compression_quality: Some(50),
@ -24,7 +24,7 @@ async fn sources() -> Result<()> {
let file = env::temp_dir().join("obws-test-image.png"); let file = env::temp_dir().join("obws-test-image.png");
client client
.save_screenshot(SaveScreenshot { .save_screenshot(SaveScreenshot {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
file_path: &file, file_path: &file,
width: None, width: None,
height: None, height: None,

@ -31,7 +31,7 @@ async fn ui() -> Result<()> {
.await?; .await?;
client client
.open_source_projector(OpenSourceProjector { .open_source_projector(OpenSourceProjector {
source: TEST_TEXT, source: TEST_TEXT.as_source(),
location: Some(Location::MonitorIndex(-1)), location: Some(Location::MonitorIndex(-1)),
}) })
.await?; .await?;

Loading…
Cancel
Save