feat: add features from obs-websocket v5.4

main
Dominik Nakamura 2 weeks ago
parent 46b7d960e1
commit a8ad9db37f
No known key found for this signature in database

@ -12,9 +12,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 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 `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.
## [0.11.5] - 2023-09-04

@ -33,6 +33,7 @@ time = "0.3.31"
tokio = { version = "1.35.1", features = ["net", "rt", "sync", "time"] }
tokio-tungstenite = "0.21.0"
tracing = "0.1.40"
uuid = { version = "1.7.0", features = ["serde"] }
[dev-dependencies]
anyhow = "1.0.79"

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

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

@ -2,9 +2,12 @@ use serde::{de::DeserializeOwned, Serialize};
use super::Client;
use crate::{
requests::filters::{
Create, CreateInternal, Request, SetEnabled, SetIndex, SetName, SetSettings,
SetSettingsInternal,
requests::{
filters::{
Create, CreateInternal, Request, SetEnabled, SetIndex, SetName, SetSettings,
SetSettingsInternal,
},
sources::SourceId,
},
responses::filters as responses,
Error, Result,
@ -16,9 +19,18 @@ pub struct 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.
#[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
.send_message::<_, responses::Filters>(Request::List { source })
.await
@ -61,7 +73,7 @@ impl<'a> Filters<'a> {
/// Removes a filter from a source.
#[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
.send_message(Request::Remove { source, filter })
.await
@ -75,7 +87,7 @@ impl<'a> Filters<'a> {
/// Gets the info for a specific source filter.
#[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
.send_message(Request::Get { source, filter })
.await

@ -22,9 +22,9 @@ impl<'a> Hotkeys<'a> {
/// Triggers a hotkey using its name. See [`Self::list`].
#[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
.send_message(Request::TriggerByName { name })
.send_message(Request::TriggerByName { name, context })
.await
}

@ -5,7 +5,7 @@ use super::Client;
use crate::{
common::MonitorType,
requests::inputs::{
Create, CreateInputInternal, Request, SetSettings, SetSettingsInternal, Volume,
Create, CreateInputInternal, InputId, Request, SetSettings, SetSettingsInternal, Volume,
},
responses::inputs as responses,
Error, Result,
@ -60,11 +60,11 @@ impl<'a> Inputs<'a> {
/// **Note:** Does not include defaults. To create the entire settings object, overlay input
/// settings over the default input settings provided by [`Inputs::default_settings`].
#[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
T: DeserializeOwned,
{
self.client.send_message(Request::Settings { name }).await
self.client.send_message(Request::Settings { input }).await
}
/// Sets the settings of an input.
@ -85,60 +85,60 @@ impl<'a> Inputs<'a> {
/// Gets the audio mute state of an input.
#[doc(alias = "GetInputMute")]
pub async fn muted(&self, name: &str) -> Result<bool> {
pub async fn muted(&self, input: InputId<'_>) -> Result<bool> {
self.client
.send_message::<_, responses::InputMuted>(Request::Muted { name })
.send_message::<_, responses::InputMuted>(Request::Muted { input })
.await
.map(|im| im.muted)
}
/// Sets the audio mute state of an input.
#[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
.send_message(Request::SetMuted { name, muted })
.send_message(Request::SetMuted { input, muted })
.await
}
/// Toggles the audio mute state of an input.
#[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
.send_message::<_, responses::InputMuted>(Request::ToggleMute { name })
.send_message::<_, responses::InputMuted>(Request::ToggleMute { input })
.await
.map(|im| im.muted)
}
/// Gets the current volume setting of an input.
#[doc(alias = "GetInputVolume")]
pub async fn volume(&self, name: &str) -> Result<responses::InputVolume> {
self.client.send_message(Request::Volume { name }).await
pub async fn volume(&self, input: InputId<'_>) -> Result<responses::InputVolume> {
self.client.send_message(Request::Volume { input }).await
}
/// Sets the volume setting of an input.
#[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
.send_message(Request::SetVolume { name, volume })
.send_message(Request::SetVolume { input, volume })
.await
}
/// Sets the name of an input (rename).
#[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
.send_message(Request::SetName { name, new })
.send_message(Request::SetName { input, new })
.await
}
/// Creates a new input, adding it as a scene item to the specified scene.
#[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
T: Serialize,
{
self.client
.send_message::<_, responses::SceneItemId>(Request::Create(CreateInputInternal {
.send_message(Request::Create(CreateInputInternal {
scene: input.scene,
input: input.input,
kind: input.kind,
@ -151,31 +151,30 @@ impl<'a> Inputs<'a> {
enabled: input.enabled,
}))
.await
.map(|sii| sii.scene_item_id)
}
/// Removes an existing input.
///
/// **Note:** Will immediately remove all associated scene items.
#[doc(alias = "RemoveInput")]
pub async fn remove(&self, name: &str) -> Result<()> {
self.client.send_message(Request::Remove { name }).await
pub async fn remove(&self, input: InputId<'_>) -> Result<()> {
self.client.send_message(Request::Remove { input }).await
}
/// Gets the audio balance of an input.
#[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
.send_message::<_, responses::AudioBalance>(Request::AudioBalance { name })
.send_message::<_, responses::AudioBalance>(Request::AudioBalance { input })
.await
.map(|ab| ab.audio_balance)
}
/// Sets the audio balance of an input.
#[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
.send_message(Request::SetAudioBalance { name, balance })
.send_message(Request::SetAudioBalance { input, balance })
.await
}
@ -183,26 +182,26 @@ impl<'a> Inputs<'a> {
///
/// **Note:** The audio sync offset can be negative too!
#[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
.send_message::<_, responses::AudioSyncOffset>(Request::AudioSyncOffset { name })
.send_message::<_, responses::AudioSyncOffset>(Request::AudioSyncOffset { input })
.await
.map(|aso| aso.input_audio_sync_offset)
}
/// Sets the audio sync offset of an input.
#[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
.send_message(Request::SetAudioSyncOffset { name, offset })
.send_message(Request::SetAudioSyncOffset { input, offset })
.await
}
/// Gets the audio monitor type of input.
#[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
.send_message::<_, responses::AudioMonitorType>(Request::AudioMonitorType { name })
.send_message::<_, responses::AudioMonitorType>(Request::AudioMonitorType { input })
.await
.map(|amt| amt.monitor_type)
}
@ -211,28 +210,35 @@ impl<'a> Inputs<'a> {
#[doc(alias = "SetInputAudioMonitorType")]
pub async fn set_audio_monitor_type(
&self,
name: &str,
input: InputId<'_>,
monitor_type: MonitorType,
) -> Result<()> {
self.client
.send_message(Request::SetAudioMonitorType { name, monitor_type })
.send_message(Request::SetAudioMonitorType {
input,
monitor_type,
})
.await
}
/// Gets the enable state of all audio tracks of an input.
#[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
.send_message::<_, responses::AudioTracks>(Request::AudioTracks { name })
.send_message::<_, responses::AudioTracks>(Request::AudioTracks { input })
.await
.map(|at| at.audio_tracks)
}
/// Sets the enable state of audio tracks of an input.
#[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
.send_message(Request::SetAudioTracks { name, tracks })
.send_message(Request::SetAudioTracks { input, tracks })
.await
}
@ -243,7 +249,7 @@ impl<'a> Inputs<'a> {
#[doc(alias = "GetInputPropertiesListPropertyItems")]
pub async fn properties_list_property_items(
&self,
input: &str,
input: InputId<'_>,
property: &str,
) -> Result<Vec<responses::ListPropertyItem>> {
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
/// button.
#[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
.send_message(Request::PressPropertiesButton { input, property })
.await

@ -2,7 +2,9 @@ use time::Duration;
use super::Client;
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,
};
@ -14,7 +16,7 @@ pub struct MediaInputs<'a> {
impl<'a> MediaInputs<'a> {
/// Gets the status of a media input.
#[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
}
@ -22,7 +24,7 @@ impl<'a> MediaInputs<'a> {
///
/// This request does not perform bounds checking of the cursor position.
#[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
.send_message(Request::SetCursor { input, cursor })
.await
@ -32,7 +34,7 @@ impl<'a> MediaInputs<'a> {
///
/// This request does not perform bounds checking of the cursor position.
#[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
.send_message(Request::OffsetCursor { input, offset })
.await
@ -40,7 +42,7 @@ impl<'a> MediaInputs<'a> {
/// Triggers an action on a media input.
#[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
.send_message(Request::TriggerAction { input, action })
.await

@ -137,7 +137,7 @@ where
const OBS_STUDIO_VERSION: Comparator = Comparator {
op: Op::GreaterEq,
major: 28,
major: 30,
minor: None,
patch: None,
pre: Prerelease::EMPTY,

@ -3,11 +3,14 @@ use serde::{de::DeserializeOwned, Serialize};
use super::Client;
use crate::{
common::BlendMode,
requests::scene_items::{
CreateSceneItem, Duplicate, Id, Request, SetBlendMode, SetEnabled, SetIndex, SetLocked,
SetPrivateSettings, SetPrivateSettingsInternal, SetTransform,
requests::{
scene_items::{
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,
};
@ -19,7 +22,7 @@ pub struct SceneItems<'a> {
impl<'a> SceneItems<'a> {
/// Gets a list of all scene items in a scene.
#[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
.send_message::<_, responses::SceneItemList>(Request::List { scene })
.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.
#[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
.send_message::<_, responses::SceneItemList>(Request::ListGroup { scene })
.await
@ -46,6 +49,12 @@ impl<'a> SceneItems<'a> {
.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.
#[doc(alias = "CreateSceneItem")]
pub async fn create(&self, create: CreateSceneItem<'_>) -> Result<i64> {
@ -57,7 +66,7 @@ impl<'a> SceneItems<'a> {
/// Removes a scene item from a scene.
#[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
.send_message(Request::Remove { scene, item_id })
.await
@ -76,7 +85,7 @@ impl<'a> SceneItems<'a> {
#[doc(alias = "GetSceneItemTransform")]
pub async fn transform(
&self,
scene: &str,
scene: SceneId<'_>,
item_id: i64,
) -> Result<responses::SceneItemTransform> {
self.client
@ -98,7 +107,7 @@ impl<'a> SceneItems<'a> {
/// Gets the enable state of a scene item.
#[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
.send_message::<_, responses::SceneItemEnabled>(Request::Enabled { scene, item_id })
.await
@ -113,7 +122,7 @@ impl<'a> SceneItems<'a> {
/// Gets the lock state of a scene item.
#[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
.send_message::<_, responses::SceneItemLocked>(Request::Locked { scene, item_id })
.await
@ -130,7 +139,7 @@ impl<'a> SceneItems<'a> {
///
/// An index of 0 is at the bottom of the source list in the UI.
#[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
.send_message::<_, responses::SceneItemIndex>(Request::Index { scene, item_id })
.await
@ -145,7 +154,7 @@ impl<'a> SceneItems<'a> {
/// Gets the blend mode of a scene item.
#[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
.send_message::<_, responses::SceneItemBlendMode>(Request::BlendMode { scene, item_id })
.await
@ -160,7 +169,7 @@ impl<'a> SceneItems<'a> {
/// Gets private scene item settings.
#[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
T: DeserializeOwned,
{

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

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

@ -1,8 +1,11 @@
use super::Client;
use crate::{
requests::ui::{
OpenSourceProjector, OpenSourceProjectorInternal, OpenVideoMixProjector,
OpenVideoMixProjectorInternal, Request,
requests::{
inputs::InputId,
ui::{
OpenSourceProjector, OpenSourceProjectorInternal, OpenVideoMixProjector,
OpenVideoMixProjectorInternal, Request,
},
},
responses::ui as responses,
Result,
@ -35,7 +38,7 @@ impl<'a> Ui<'a> {
/// Opens the properties dialog of an input.
#[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
.send_message(Request::OpenInputPropertiesDialog { input })
.await
@ -43,7 +46,7 @@ impl<'a> Ui<'a> {
/// Opens the filters dialog of an input.
#[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
.send_message(Request::OpenInputFiltersDialog { input })
.await
@ -51,7 +54,7 @@ impl<'a> Ui<'a> {
/// Opens the interact dialog of an input.
#[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
.send_message(Request::OpenInputInteractDialog { input })
.await

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -42,8 +42,12 @@ async fn inputs() -> Result<()> {
.set_volume(TEST_MEDIA, Volume::Mul(volume.mul))
.await?;
client.set_name(TEST_BROWSER, TEST_BROWSER_RENAME).await?;
client.set_name(TEST_BROWSER_RENAME, TEST_BROWSER).await?;
client
.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?;
client.set_audio_balance(TEST_MEDIA, balance / 2.0).await?;

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

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

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

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

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

Loading…
Cancel
Save